mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73d34434b0 | ||
|
|
d0154e5721 | ||
|
|
52b6a8a076 | ||
|
|
db6a9cfc68 | ||
|
|
03b8e53d30 | ||
|
|
8d79ace8b3 | ||
|
|
1fb6d3d62d | ||
|
|
c4559cb0d7 | ||
|
|
1d88ddefb5 | ||
|
|
683352f737 | ||
|
|
a629bc940c | ||
|
|
fe7b8eb6f3 | ||
|
|
24339929dc | ||
|
|
ed855f6859 | ||
|
|
e144c1822f | ||
|
|
69271d33ca | ||
|
|
f1140f106a | ||
|
|
3b935b7979 | ||
|
|
00388f9166 | ||
|
|
a32efa2ba0 | ||
|
|
76704b6bdd | ||
|
|
168d8cbb08 | ||
|
|
9629c22496 | ||
|
|
a34d06f9b4 | ||
|
|
2ba3e80ad9 | ||
|
|
12e8e3e064 | ||
|
|
aa24995d28 | ||
|
|
d493642935 | ||
|
|
86670486a6 | ||
|
|
6496011d8f | ||
|
|
792351ab8a | ||
|
|
4915dc574e | ||
|
|
9866e8068f | ||
|
|
099af3ce01 | ||
|
|
445e634aa1 | ||
|
|
4bb1fee995 | ||
|
|
aad14395e0 | ||
|
|
598f2a0eb6 | ||
|
|
cb1cef6f1b | ||
|
|
fd034d493f | ||
|
|
2551670874 | ||
|
|
3ec7e9b8d6 | ||
|
|
499ee692da | ||
|
|
de13142950 | ||
|
|
597e41f053 | ||
|
|
b0a33a58d1 | ||
|
|
65b9f21981 | ||
|
|
33b5407245 | ||
|
|
7aafc3b69d | ||
|
|
9a093459bc | ||
|
|
aa36b0cfdc | ||
|
|
93c6409688 | ||
|
|
84d5e1f3d7 | ||
|
|
6e5413ec1e | ||
|
|
84e8ce1d43 | ||
|
|
c69ea72860 | ||
|
|
624fecfa70 | ||
|
|
6dcf13481d | ||
|
|
3fd0f715a1 | ||
|
|
85e7f245c0 | ||
|
|
03df1006cc | ||
|
|
3099cb1319 | ||
|
|
49b8f0bcf1 | ||
|
|
a23eb91f04 | ||
|
|
974d3017e0 | ||
|
|
668c3a57d3 | ||
|
|
819ce93a90 | ||
|
|
061a8dd8f1 | ||
|
|
49df78c075 | ||
|
|
6a6b61c491 | ||
|
|
7612e25dd1 | ||
|
|
705e489c17 | ||
|
|
9a418aba41 | ||
|
|
2cb6c4fef1 | ||
|
|
180f47b6e0 |
+56
-1
@@ -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)
|
||||
@@ -114,3 +136,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")
|
||||
|
||||
@@ -33,3 +33,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
.tmp
|
||||
.tmp/*
|
||||
@@ -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**
|
||||
@@ -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
@@ -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 50–100 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.
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "ritmex-bot",
|
||||
"dependencies": {
|
||||
"@grvt/client": "^1.6.4",
|
||||
"@nadohq/client": "^0.1.0-alpha.41",
|
||||
"@noble/ed25519": "^3.0.0",
|
||||
"axios": "^1.12.2",
|
||||
"bignumber.js": "^9.3.1",
|
||||
"ccxt": "^4.5.12",
|
||||
"dotenv": "^17.2.2",
|
||||
"ethereum-cryptography": "^2.1.3",
|
||||
"ink": "^6.3.1",
|
||||
"react": "^19.1.1",
|
||||
"trading-signals": "^7.4.3",
|
||||
"viem": "^2.43.1",
|
||||
"ws": "^8.18.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -24,7 +29,9 @@
|
||||
},
|
||||
},
|
||||
"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=="],
|
||||
|
||||
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
|
||||
|
||||
@@ -78,10 +85,22 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
|
||||
|
||||
"@grvt/client": ["@grvt/client@1.6.4", "", { "dependencies": { "axios": "^1.12.2" } }, "sha512-yZfEvsC/BtkcdMjoB7Eh6gshBraz/9810qCllvNXDx6OmdxR+NfxPO+ucNlPbyjEt1HfSVzHBWB1yGnVl1usNA=="],
|
||||
"@grvt/client": ["@grvt/client@1.6.25", "", { "dependencies": { "axios": "^1.13.2" } }, "sha512-LG3oZSJDq1Qz6mLiHDmg+v/Hp+Nd+yOp8GIvkBVyIdOcPVzuzC8UQD7IxvS+g5EfQQKLiKzXSjKLc39a11Uj1A=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@nadohq/client": ["@nadohq/client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/indexer-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "@nadohq/trigger-client": "^0.1.0-alpha.45", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-63S7eT5xSk3hxEXRLGDI94mPeu/zd1Nu4oUOZrY8wzhaT7Gl7PVP2snb8JH/PduVQzgs7+qblr4aTBI/j6QvLQ=="],
|
||||
|
||||
"@nadohq/engine-client": ["@nadohq/engine-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-GqaRrsB0Z2EgNY6wu4nG5OTc5hF+buNRMc0+jPLlOQonlPJAC4OiMCgCigm4nJecYwsdiQD05W3Z4YUqggxbyw=="],
|
||||
|
||||
"@nadohq/indexer-client": ["@nadohq/indexer-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-8PzG9taYh380lsKT8i4UvGfhZiPpt+zPisx+ErVqMifyhcJAhgMeYNcfijwMQFNJdgTaBvtI0R5JK98l4Y0YQw=="],
|
||||
|
||||
"@nadohq/shared": ["@nadohq/shared@0.1.0-alpha.45", "", { "peerDependencies": { "bignumber.js": "^9.3.0", "viem": "*" } }, "sha512-4Ml8dO9mkwnIidflpunwVMLX2KJA79b53T9xTBkH1Yzy0iOnbEE27r25wvgJgyszU0aznbPBv2++FZ0d0B5Zhg=="],
|
||||
|
||||
"@nadohq/trigger-client": ["@nadohq/trigger-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-Xt8W9zxOZhMvoXSRnx9kUR26wWuHc3EgTVTBbUXt7SlI9v3IP9I3idS6SU3j2f08flcvNqRmC2EbXyYDC2XeyQ=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -138,7 +157,7 @@
|
||||
|
||||
"@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=="],
|
||||
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
@@ -152,8 +171,6 @@
|
||||
|
||||
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
|
||||
@@ -168,7 +185,9 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
@@ -180,15 +199,17 @@
|
||||
|
||||
"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=="],
|
||||
"axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"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=="],
|
||||
"ccxt": ["ccxt@4.5.35", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-aFn1jq/vR9wD3Vxu/2UFoS8snqlvfYn/iYrNhyZZfzB3N+kAHhP5+sAgO6ZwNHkHiUT9IPPahC1ZSFdPtjpIYA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -200,7 +221,7 @@
|
||||
|
||||
"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,15 +229,13 @@
|
||||
|
||||
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="],
|
||||
"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -244,6 +263,8 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
|
||||
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
@@ -272,12 +293,14 @@
|
||||
|
||||
"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=="],
|
||||
"ink": ["ink@6.6.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.2.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", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^8.1.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-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||
|
||||
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
|
||||
|
||||
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
|
||||
|
||||
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
|
||||
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
||||
@@ -298,6 +321,8 @@
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"ox": ["ox@0.11.3", "", { "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-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw=="],
|
||||
|
||||
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
@@ -312,15 +337,15 @@
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
||||
"react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
@@ -336,7 +361,7 @@
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"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=="],
|
||||
"string-width": ["string-width@8.1.1", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
@@ -354,12 +379,18 @@
|
||||
|
||||
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
|
||||
|
||||
"trading-signals": ["trading-signals@7.4.3", "", {}, "sha512-kOyzd85qhuhU7yGkB5z74MVP6j30xBCfZy5+bgBnvfxY1ZdvaQsdD+C5j+CoIRtCAgSOuxB7jmPpBrqO6h17sQ=="],
|
||||
|
||||
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||
|
||||
"viem": ["viem@2.45.1", "", { "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.11.3", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-LN6Pp7vSfv50LgwhkfSbIXftAM5J89lP9x8TeDa8QM7o41IxlHrDh0F9X+FfnCWtsz11pEVV5sn+yBUoOHNqYA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
@@ -372,12 +403,38 @@
|
||||
|
||||
"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=="],
|
||||
"ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
|
||||
|
||||
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"widest-line/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
*.go text eol=lf
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.idea
|
||||
vendor
|
||||
build/*
|
||||
!build/.keep
|
||||
/build
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,94 @@
|
||||
# lighter-go
|
||||
|
||||
This repository serves as the reference implementation of signing & hashing of Lighter transactions.
|
||||
The sharedlib is compiled for a variety of platforms.
|
||||
- macOS (darwin) dynamic library (.dylib) for arm architecture (M processor, not Intel)
|
||||
- linux shared object (.so) for both amd64 and arm architectures
|
||||
- windows .ddl (dynamic-link library) for amd64 architecture
|
||||
|
||||
The go SDK implements just the core signing, as well as a small HTTP client so that users can:
|
||||
- not specify the nonce of the transaction (this will result in an HTTP call, so beware)
|
||||
- check that a client was initialized correctly, by verifying that the given API key matches the one on the server
|
||||
|
||||
The [Python SDK](https://github.com/elliottech/lighter-python) offers support for HTTP and WebSocket functionality as well as [examples](https://github.com/elliottech/lighter-python/tree/main/examples) on how to generate the API keys, how to create and cancel orders, generate AUTH tokens for various HTTP/WS endpoints which require them.
|
||||
|
||||
All generated shared libraries follow the naming convention `lighter_signer_{os}_{arch}` where os is linux/windows/darwin and arch is amd64(x86) or arm64.\
|
||||
The build & accompanying `.h` files can be found in the release notes [here](https://github.com/elliottech/lighter-go/releases).\
|
||||
If you'd like to compile your own binaries, the commands are in the `justfile`.
|
||||
|
||||
|
||||
## Transactions
|
||||
```
|
||||
=== Client ===
|
||||
CreateClient
|
||||
CheckClient
|
||||
|
||||
=== API Key ===
|
||||
CreateAuthToken
|
||||
SignChangePubKey
|
||||
GenerateAPIKey
|
||||
|
||||
=== Order ===
|
||||
SignCreateOrder
|
||||
SignCreateGroupedOrders
|
||||
SignCancelOrder
|
||||
SignCancelAllOrders
|
||||
SignModifyOrder
|
||||
|
||||
=== Leverage & Margin ===
|
||||
SignUpdateLeverage
|
||||
SignUpdateMargin
|
||||
|
||||
=== Transfers ===
|
||||
SignWithdraw
|
||||
SignTransfer
|
||||
|
||||
=== Sub account & pools ===
|
||||
SignCreateSubAccount
|
||||
SignCreatePublicPool
|
||||
SignUpdatePublicPool
|
||||
SignMintShares
|
||||
SignBurnShares
|
||||
```
|
||||
|
||||
## How to specify an account
|
||||
Accounts are loaded into the signer by calling the `CreateClient` method. If you wish to load multiple API keys in the signer, you need to call the method multiple times, each time with the correct private key.
|
||||
|
||||
By default, signer will work out of the box with 1 client and no need to manage nonces in any specific way. Just pass `-1, 255, 0` for all methods (more explanations below).
|
||||
|
||||
You can call `CheckClient` to verify that the provided Private key & (apiKeyIndex, accountIndex) are configured correctly.
|
||||
This checks that the public key associated with the pair (apiKey,account) matches the one from the exchange.
|
||||
|
||||
The majority of methods receive 3 arguments at the end:
|
||||
- `nonce`
|
||||
- default `-1`
|
||||
- required to be strictly incremental
|
||||
- you can fetch the next nonce using `nextNonce` HTTP call
|
||||
- if default is passed, signer will do the HTTP call automatically
|
||||
- ideally the caller should manage nonces locally to avoid latency
|
||||
- `apiKeyIndex`
|
||||
- default `255`
|
||||
- specified which API key will be used for the specific transaction
|
||||
- all API keys are equal. Orders created by one API key can be canceled by others, for example
|
||||
- each API key has its own nonce
|
||||
- if default is passed, signer will use the default txClient
|
||||
- `accountIndex`
|
||||
- default `0`
|
||||
- specified which account will be used for the specific transaction
|
||||
- this can be a subaccount or a different main account all together
|
||||
- if default is passed, signer will use the default txClient
|
||||
|
||||
**Note:** in order to use the default client, you need to bash both the default values for `apiKeyIndex` and `accountIndex`
|
||||
|
||||
## Auth tokens
|
||||
|
||||
Auth tokens are used to call various HTTP & WS endpoints which hold sensitive information, like open orders.
|
||||
An auth token is valid for 8 hours.
|
||||
|
||||
`CreateAuthToken(deadline=0)` will result in a token that's valid for 7 hours from now.
|
||||
|
||||
Calling `CreateAuthToken` with an expiry 20 hours in the future will work, but the token will start to be valid in 12 hours, because the max accepted deadline duration by the server is 8 hours. \
|
||||
This still allows you to generate all the tokens ahead of time and use them accordingly. \
|
||||
Such an approach (both implementation & how to manage them) can be found in great details in the [python-sdk](https://github.com/elliottech/lighter-python/tree/main/examples/read-only-auth).
|
||||
|
||||
**Note:** auth tokens are bound to an API key. Changing the API key to something else **will invalidate** all generated auth tokens.
|
||||
@@ -0,0 +1,116 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
curve "github.com/elliottech/poseidon_crypto/curve/ecgfp5"
|
||||
schnorr "github.com/elliottech/poseidon_crypto/signature/schnorr"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// SharedClientManager holds the global txClient and backupTxClients
|
||||
// This will be managed by both sharedlib and wasm builds
|
||||
// Supports multiple accounts and API keys with thread safety
|
||||
var (
|
||||
txClientMu sync.Mutex
|
||||
defaultTxClient *TxClient
|
||||
defaultClientPerAccount = make(map[int64]*TxClient)
|
||||
allTxClients map[int64]map[uint8]*TxClient // accountIndex -> apiKeyIndex -> client
|
||||
)
|
||||
|
||||
// GenerateAPIKey generates a new API key pair from a seed
|
||||
func GenerateAPIKey(seed string) (string, string, error) {
|
||||
var seedP *string
|
||||
if seed != "" {
|
||||
seedP = &seed
|
||||
}
|
||||
|
||||
key := curve.SampleScalar(seedP)
|
||||
publicKeyStr := hexutil.Encode(schnorr.SchnorrPkFromSk(key).ToLittleEndianBytes())
|
||||
privateKeyStr := hexutil.Encode(key.ToLittleEndianBytes())
|
||||
|
||||
return privateKeyStr, publicKeyStr, nil
|
||||
}
|
||||
|
||||
// GetClient retrieves a client for specific account and API key
|
||||
// If apiKeyIndex==255 && accountIndex==-1, returns default client
|
||||
func GetClient(apiKeyIndex uint8, accountIndex int64) (*TxClient, error) {
|
||||
txClientMu.Lock()
|
||||
defer txClientMu.Unlock()
|
||||
|
||||
if apiKeyIndex == 255 && accountIndex != -1 {
|
||||
client := defaultClientPerAccount[accountIndex]
|
||||
if client != nil {
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: return default client
|
||||
if apiKeyIndex == 255 && accountIndex == -1 {
|
||||
if defaultTxClient == nil {
|
||||
return nil, fmt.Errorf("client is not created, call CreateClient() first")
|
||||
}
|
||||
return defaultTxClient, nil
|
||||
}
|
||||
|
||||
// Look up client in double map
|
||||
var c *TxClient
|
||||
if allTxClients[accountIndex] != nil {
|
||||
c = allTxClients[accountIndex][apiKeyIndex]
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("client is not created for apiKeyIndex: %v accountIndex: %v", apiKeyIndex, accountIndex)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// CreateClient creates a new TxClient and stores it
|
||||
// httpClientFactory is a function that creates an HTTP client from a URL string
|
||||
func CreateClient(httpClient MinimalHTTPClient, privateKey string, chainId uint32, apiKeyIndex uint8, accountIndex int64) (*TxClient, error) {
|
||||
if accountIndex <= 0 {
|
||||
return nil, fmt.Errorf("invalid account index")
|
||||
}
|
||||
|
||||
txClientInstance, err := NewTxClient(httpClient, privateKey, accountIndex, apiKeyIndex, chainId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error occurred when creating TxClient. err: %v", err)
|
||||
}
|
||||
|
||||
txClientMu.Lock()
|
||||
if allTxClients == nil {
|
||||
allTxClients = make(map[int64]map[uint8]*TxClient)
|
||||
}
|
||||
if allTxClients[accountIndex] == nil {
|
||||
allTxClients[accountIndex] = make(map[uint8]*TxClient)
|
||||
}
|
||||
allTxClients[accountIndex][apiKeyIndex] = txClientInstance
|
||||
|
||||
// Update default client (most recently created becomes default)
|
||||
defaultTxClient = txClientInstance
|
||||
defaultClientPerAccount[accountIndex] = txClientInstance
|
||||
txClientMu.Unlock()
|
||||
|
||||
return txClientInstance, nil
|
||||
}
|
||||
|
||||
// Check validates that the client exists and the API key matches the one on the server
|
||||
func (c *TxClient) Check() error {
|
||||
// check that the API key registered on Lighter matches this one
|
||||
publicKey, err := c.HTTP().GetApiKey(c.accountIndex, c.apiKeyIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get Api Keys. err: %v", err)
|
||||
}
|
||||
|
||||
pubKeyBytes := c.GetKeyManager().PubKeyBytes()
|
||||
pubKeyStr := hexutil.Encode(pubKeyBytes[:])
|
||||
pubKeyStr = strings.Replace(pubKeyStr, "0x", "", 1)
|
||||
|
||||
if publicKey != pubKeyStr {
|
||||
return fmt.Errorf("private key does not match the one on Lighter. ownPubKey: %s response: %+v", pubKeyStr, publicKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# HTTP
|
||||
The HTTP module is a simple implementation that aims to provide just 2 things:
|
||||
- `GetNextNonce` so that users can send transactions w/out calling managing nonces on their side
|
||||
- `GetApiKey` so that users can call `CheckClient` from other sources, which makes sure that the client was configured correctly.
|
||||
|
||||
Other usages, like sending trades, fetching open orders or any WebSocket operations should happen outside the core SDK.
|
||||
@@ -0,0 +1,45 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
core "github.com/elliottech/lighter-go/client"
|
||||
)
|
||||
|
||||
var (
|
||||
dialer = &net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
KeepAlive: 60 * time.Second,
|
||||
}
|
||||
transport = &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
MaxConnsPerHost: 1000,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Transport: transport,
|
||||
}
|
||||
)
|
||||
|
||||
var _ core.MinimalHTTPClient = (*client)(nil)
|
||||
|
||||
type client struct {
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func NewClient(baseUrl string) core.MinimalHTTPClient {
|
||||
if baseUrl == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &client{
|
||||
endpoint: baseUrl,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package http
|
||||
|
||||
const (
|
||||
CodeOK = 200
|
||||
)
|
||||
|
||||
type ResultCode struct {
|
||||
Code int32 `json:"code,example=200"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type NextNonce struct {
|
||||
ResultCode
|
||||
Nonce int64 `json:"nonce,example=722"`
|
||||
}
|
||||
|
||||
type ApiKey struct {
|
||||
AccountIndex int64 `json:"account_index,example=3"`
|
||||
ApiKeyIndex uint8 `json:"api_key_index,example=0"`
|
||||
Nonce int64 `json:"nonce,example=722"`
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
|
||||
type AccountApiKeys struct {
|
||||
ResultCode
|
||||
ApiKeys []*ApiKey `json:"api_keys"`
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (c *client) parseResultStatus(respBody []byte) error {
|
||||
resultStatus := &ResultCode{}
|
||||
if err := json.Unmarshal(respBody, resultStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
if resultStatus.Code != CodeOK {
|
||||
return errors.New(resultStatus.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) getAndParseL2HTTPResponse(path string, params map[string]any, result interface{}) error {
|
||||
u, err := url.Parse(c.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Path = path
|
||||
|
||||
q := u.Query()
|
||||
for k, v := range params {
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
resp, err := httpClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(string(body))
|
||||
}
|
||||
if err = c.parseResultStatus(body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(body, result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetNextNonce(accountIndex int64, apiKeyIndex uint8) (int64, error) {
|
||||
result := &NextNonce{}
|
||||
err := c.getAndParseL2HTTPResponse("api/v1/nextNonce", map[string]any{"account_index": accountIndex, "api_key_index": apiKeyIndex}, result)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return result.Nonce, nil
|
||||
}
|
||||
|
||||
func (c *client) GetApiKey(accountIndex int64, apiKeyIndex uint8) (string, error) {
|
||||
result := &AccountApiKeys{}
|
||||
err := c.getAndParseL2HTTPResponse("api/v1/apikeys", map[string]any{"account_index": accountIndex, "api_key_index": apiKeyIndex}, result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(result.ApiKeys) == 0 {
|
||||
return "", fmt.Errorf("no api keys returned")
|
||||
}
|
||||
return result.ApiKeys[0].PublicKey, nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package client
|
||||
|
||||
type MinimalHTTPClient interface {
|
||||
GetNextNonce(accountIndex int64, apiKeyIndex uint8) (int64, error)
|
||||
GetApiKey(accountIndex int64, apiKeyIndex uint8) (string, error)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/elliottech/lighter-go/signer"
|
||||
"github.com/elliottech/lighter-go/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultExpireTime is a public var, so it can be changed directly in the SDK if required.
|
||||
// The encouraged behaviour is the manually specify the TX deadline in types.TransactOpts.ExpiredAt
|
||||
DefaultExpireTime = time.Minute*10 - time.Second // we need to give a second margin, to eliminate millisecond differences
|
||||
)
|
||||
|
||||
type TxClient struct {
|
||||
apiClient MinimalHTTPClient
|
||||
chainId uint32
|
||||
keyManager signer.KeyManager
|
||||
accountIndex int64
|
||||
apiKeyIndex uint8
|
||||
}
|
||||
|
||||
// NewTxClient is linked to a specific (account, apiKey) pair
|
||||
// apiKeyPrivateKey should be hex-encoded bytes generated using `hexutil.Encode(TxClient.GetKeyManager().PrvKeyBytes())`
|
||||
func NewTxClient(apiClient MinimalHTTPClient, apiKeyPrivateKey string, accountIndex int64, apiKeyIndex uint8, chainId uint32) (*TxClient, error) {
|
||||
// remove 0x from private key, if any, and parse to bytes
|
||||
if len(apiKeyPrivateKey) < 2 {
|
||||
return nil, fmt.Errorf("empty private key")
|
||||
}
|
||||
if apiKeyPrivateKey[:2] == "0x" {
|
||||
apiKeyPrivateKey = apiKeyPrivateKey[2:]
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(apiKeyPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyManager, err := signer.NewKeyManager(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TxClient{
|
||||
apiClient: apiClient,
|
||||
apiKeyIndex: apiKeyIndex,
|
||||
accountIndex: accountIndex,
|
||||
chainId: chainId,
|
||||
keyManager: keyManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FullFillDefaultOps returns a usable TransactOpts object if none was provided.
|
||||
// This should not the be case for sharedlib, except for the nonce, which is optional.
|
||||
// Still, the behaviour is implemented, so it can be extended easily by extending the code GO SDK.
|
||||
func (c *TxClient) FullFillDefaultOps(ops *types.TransactOpts) (*types.TransactOpts, error) {
|
||||
if ops == nil {
|
||||
ops = new(types.TransactOpts)
|
||||
}
|
||||
if ops.ExpiredAt == 0 {
|
||||
ops.ExpiredAt = time.Now().Add(DefaultExpireTime).UnixMilli()
|
||||
}
|
||||
if ops.FromAccountIndex == nil {
|
||||
ops.FromAccountIndex = &c.accountIndex
|
||||
}
|
||||
if ops.ApiKeyIndex == nil {
|
||||
ops.ApiKeyIndex = &c.apiKeyIndex
|
||||
}
|
||||
if ops.Nonce == nil || *ops.Nonce == -1 {
|
||||
if c.apiClient == nil {
|
||||
return nil, fmt.Errorf("nonce was not provided & HTTPClient is nil. Either provide the nonce or enable HTTPClient to get the nonce from Lighter")
|
||||
}
|
||||
nonce, err := c.apiClient.GetNextNonce(*ops.FromAccountIndex, *ops.ApiKeyIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetChainId() uint32 {
|
||||
return c.chainId
|
||||
}
|
||||
|
||||
func (c *TxClient) GetKeyManager() signer.KeyManager {
|
||||
return c.keyManager
|
||||
}
|
||||
|
||||
func (c *TxClient) GetAccountIndex() int64 {
|
||||
return c.accountIndex
|
||||
}
|
||||
|
||||
func (c *TxClient) GetApiKeyIndex() uint8 {
|
||||
return c.apiKeyIndex
|
||||
}
|
||||
|
||||
func (c *TxClient) HTTP() MinimalHTTPClient {
|
||||
return c.apiClient
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
schnorr "github.com/elliottech/poseidon_crypto/signature/schnorr"
|
||||
|
||||
"github.com/elliottech/lighter-go/types"
|
||||
"github.com/elliottech/lighter-go/types/txtypes"
|
||||
)
|
||||
|
||||
func (c *TxClient) GetAuthToken(deadline time.Time) (string, error) {
|
||||
return types.ConstructAuthToken(c.keyManager, deadline, &types.TransactOpts{
|
||||
ApiKeyIndex: &c.apiKeyIndex,
|
||||
FromAccountIndex: &c.accountIndex,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *TxClient) GetChangePubKeyTransaction(tx *types.ChangePubKeyReq, ops *types.TransactOpts) (*txtypes.L2ChangePubKeyTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructChangePubKeyTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk := c.keyManager.PubKeyBytes()
|
||||
msgHash, _ := txInfo.Hash(c.chainId)
|
||||
|
||||
if err := schnorr.Validate(pk[:], msgHash, txInfo.Sig); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate signature. error: %v", err)
|
||||
}
|
||||
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCreateSubAccountTransaction(ops *types.TransactOpts) (*txtypes.L2CreateSubAccountTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructCreateSubAccountTx(c.keyManager, c.chainId, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCreatePublicPoolTransaction(tx *types.CreatePublicPoolTxReq, ops *types.TransactOpts) (*txtypes.L2CreatePublicPoolTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructCreatePublicPoolTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetUpdatePublicPoolTransaction(tx *types.UpdatePublicPoolTxReq, ops *types.TransactOpts) (*txtypes.L2UpdatePublicPoolTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructUpdatePublicPoolTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetTransferTransaction(tx *types.TransferTxReq, ops *types.TransactOpts) (*txtypes.L2TransferTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructTransferTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetWithdrawTransaction(tx *types.WithdrawTxReq, ops *types.TransactOpts) (*txtypes.L2WithdrawTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructWithdrawTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCreateOrderTransaction(tx *types.CreateOrderTxReq, ops *types.TransactOpts) (*txtypes.L2CreateOrderTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructCreateOrderTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCreateGroupedOrdersTransaction(tx *types.CreateGroupedOrdersTxReq, ops *types.TransactOpts) (*txtypes.L2CreateGroupedOrdersTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructL2CreateGroupedOrdersTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCancelOrderTransaction(tx *types.CancelOrderTxReq, ops *types.TransactOpts) (*txtypes.L2CancelOrderTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructL2CancelOrderTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetModifyOrderTransaction(tx *types.ModifyOrderTxReq, ops *types.TransactOpts) (*txtypes.L2ModifyOrderTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txInfo, err := types.ConstructL2ModifyOrderTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetCancelAllOrdersTransaction(tx *types.CancelAllOrdersTxReq, ops *types.TransactOpts) (*txtypes.L2CancelAllOrdersTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructL2CancelAllOrdersTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetMintSharesTransaction(tx *types.MintSharesTxReq, ops *types.TransactOpts) (*txtypes.L2MintSharesTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructMintSharesTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetBurnSharesTransaction(tx *types.BurnSharesTxReq, ops *types.TransactOpts) (*txtypes.L2BurnSharesTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructBurnSharesTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetUpdateLeverageTransaction(tx *types.UpdateLeverageTxReq, ops *types.TransactOpts) (*txtypes.L2UpdateLeverageTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructUpdateLeverageTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
|
||||
func (c *TxClient) GetUpdateMarginTransaction(tx *types.UpdateMarginTxReq, ops *types.TransactOpts) (*txtypes.L2UpdateMarginTxInfo, error) {
|
||||
ops, err := c.FullFillDefaultOps(ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txInfo, err := types.ConstructUpdateMarginTx(c.keyManager, c.chainId, tx, ops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
module github.com/elliottech/lighter-go
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.1
|
||||
|
||||
require (
|
||||
github.com/elliottech/poseidon_crypto v0.0.11
|
||||
github.com/ethereum/go-ethereum v1.15.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bits-and-blooms/bitset v1.17.0 // indirect
|
||||
github.com/consensys/bavard v0.1.22 // indirect
|
||||
github.com/consensys/gnark-crypto v0.14.0 // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
|
||||
github.com/ethereum/go-verkle v0.2.2 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||
github.com/supranational/blst v0.3.14 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI=
|
||||
github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A=
|
||||
github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
|
||||
github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E=
|
||||
github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/elliottech/poseidon_crypto v0.0.11 h1:iX4rCg0m1XIX/7mhXVUEYUJIdQD57zNGNLeb6RZRl7g=
|
||||
github.com/elliottech/poseidon_crypto v0.0.11/go.mod h1:NhWxSjPGr5JXRuB2Aepl/+ZrbmUG3hvku/GarB1JR8c=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/go-ethereum v1.15.6 h1:jgLoUM6/pNjp0uEnXyWcWikDwa4j1wZlcqkX8Pm8A+I=
|
||||
github.com/ethereum/go-ethereum v1.15.6/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
|
||||
github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
|
||||
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
||||
@@ -0,0 +1,49 @@
|
||||
### Local builds
|
||||
|
||||
build-darwin-local:
|
||||
go mod vendor
|
||||
go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-darwin-arm64.dylib ./sharedlib/main.go
|
||||
|
||||
# Note: build-linux-local does not append -arm or amd64 at end
|
||||
build-linux-local:
|
||||
go mod vendor
|
||||
CGO_ENABLED=1 go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-linux.so ./sharedlib/main.go
|
||||
|
||||
# Note: build-windows-local does not append -arm or amd64 at end
|
||||
# Windows build (requires gcc from msys2: choco install msys2)
|
||||
# CMD: set PATH=C:\msys64\mingw64\bin;%PATH% && set CGO_ENABLED=1 && go mod vendor && go build -buildmode=c-shared -trimpath -o ./build/signer-amd64.dll ./sharedlib/main.go
|
||||
# PowerShell: $env:Path='C:\msys64\mingw64\bin;'+$env:Path; $env:CGO_ENABLED='1'; go mod vendor; go build -buildmode=c-shared -trimpath -o ./build/signer-amd64.dll ./sharedlib/main.go
|
||||
build-windows-local:
|
||||
go mod vendor
|
||||
$env:Path='C:\msys64\mingw64\bin;'+$env:Path; $env:CGO_ENABLED='1'; go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-windows.dll ./sharedlib/main.go
|
||||
|
||||
### Docker builds
|
||||
|
||||
# Note: I don't think this works TBH
|
||||
#build-darwin-arm64-docker:
|
||||
# docker run --rm -v ${PWD}:/go/src/sdk -w /go/src/sdk golang:1.23.2-bullseye bash -c " \
|
||||
# cd /go/src/sdk && \
|
||||
# go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-darwin-arm64.dylib ./sharedlib"
|
||||
|
||||
build-linux-amd64-docker:
|
||||
go mod vendor
|
||||
docker run --rm --platform linux/amd64 -v ${PWD}:/go/src/sdk -w /go/src/sdk golang:1.23.2-bullseye /bin/sh -c " \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-linux-amd64.so ./sharedlib"
|
||||
|
||||
build-linux-arm64-docker:
|
||||
go mod vendor
|
||||
docker run --rm --platform linux/arm64 -v ${PWD}:/go/src/sdk -w /go/src/sdk golang:1.23.2-bullseye /bin/sh -c " \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-linux-arm64.so ./sharedlib"
|
||||
|
||||
build-windows-amd64-docker:
|
||||
go mod vendor
|
||||
docker run --rm --platform linux/amd64 -v ${PWD}:/go/src/sdk -w /go/src/sdk golang:1.23.2-bullseye bash -c " \
|
||||
apt-get update && \
|
||||
apt-get install -y gcc-mingw-w64-x86-64 && \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -buildmode=c-shared -trimpath -o ./build/lighter-signer-windows-amd64.dll ./sharedlib"
|
||||
|
||||
### WASM builds
|
||||
|
||||
build-wasm:
|
||||
go mod vendor
|
||||
GOOS=js GOARCH=wasm go build -trimpath -o ./build/lighter-signer.wasm ./wasm/
|
||||
@@ -0,0 +1,690 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/elliottech/lighter-go/client"
|
||||
"github.com/elliottech/lighter-go/client/http"
|
||||
"github.com/elliottech/lighter-go/types"
|
||||
"github.com/elliottech/lighter-go/types/txtypes"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
typedef struct {
|
||||
char* str;
|
||||
char* err;
|
||||
} StrOrErr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t txType;
|
||||
char* txInfo;
|
||||
char* txHash;
|
||||
char* messageToSign;
|
||||
char* err;
|
||||
} SignedTxResponse;
|
||||
|
||||
typedef struct {
|
||||
char* privateKey;
|
||||
char* publicKey;
|
||||
char* err;
|
||||
} ApiKeyResponse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t MarketIndex;
|
||||
int64_t ClientOrderIndex;
|
||||
int64_t BaseAmount;
|
||||
uint32_t Price;
|
||||
uint8_t IsAsk;
|
||||
uint8_t Type;
|
||||
uint8_t TimeInForce;
|
||||
uint8_t ReduceOnly;
|
||||
uint32_t TriggerPrice;
|
||||
int64_t OrderExpiry;
|
||||
} CreateOrderTxReq;
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var chainId uint32
|
||||
|
||||
func wrapErr(err any) *C.char {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return C.CString(fmt.Sprintf("%v", err))
|
||||
}
|
||||
|
||||
func messageToSign(txInfo txtypes.TxInfo) string {
|
||||
switch typed := txInfo.(type) {
|
||||
case *txtypes.L2ChangePubKeyTxInfo:
|
||||
return typed.GetL1SignatureBody()
|
||||
case *txtypes.L2TransferTxInfo:
|
||||
return typed.GetL1SignatureBody(chainId)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func signedTxResponseErr(err any) C.SignedTxResponse {
|
||||
return C.SignedTxResponse{err: wrapErr(err)}
|
||||
}
|
||||
|
||||
func signedTxResponsePanic(err any) C.SignedTxResponse {
|
||||
return signedTxResponseErr(fmt.Errorf("panic: %v", err))
|
||||
}
|
||||
|
||||
func convertTxInfoToResponse(txInfo txtypes.TxInfo, err error) C.SignedTxResponse {
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
if txInfo == nil {
|
||||
return signedTxResponseErr("nil transaction info")
|
||||
}
|
||||
|
||||
txInfoStr, err := txInfo.GetTxInfo()
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
resp := C.SignedTxResponse{
|
||||
txType: C.uint8_t(txInfo.GetTxType()),
|
||||
txInfo: C.CString(txInfoStr),
|
||||
txHash: C.CString(txInfo.GetTxHash()),
|
||||
}
|
||||
|
||||
if msg := messageToSign(txInfo); msg != "" {
|
||||
resp.messageToSign = C.CString(msg)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// getClient returns the go TxClient from the specified cApiKeyIndex and cAccountIndex
|
||||
func getClient(cApiKeyIndex C.int, cAccountIndex C.longlong) (*client.TxClient, error) {
|
||||
apiKeyIndex := uint8(cApiKeyIndex)
|
||||
accountIndex := int64(cAccountIndex)
|
||||
return client.GetClient(apiKeyIndex, accountIndex)
|
||||
}
|
||||
|
||||
func getTransactOpts(cNonce C.longlong) *types.TransactOpts {
|
||||
nonce := int64(cNonce)
|
||||
return &types.TransactOpts{
|
||||
Nonce: &nonce,
|
||||
}
|
||||
}
|
||||
|
||||
//export GenerateAPIKey
|
||||
func GenerateAPIKey(cSeed *C.char) (ret C.ApiKeyResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = C.ApiKeyResponse{err: wrapErr(fmt.Errorf("panic: %v", r))}
|
||||
}
|
||||
}()
|
||||
|
||||
seed := C.GoString(cSeed)
|
||||
privateKeyStr, publicKeyStr, err := client.GenerateAPIKey(seed)
|
||||
if err != nil {
|
||||
return C.ApiKeyResponse{err: wrapErr(err)}
|
||||
}
|
||||
|
||||
return C.ApiKeyResponse{
|
||||
privateKey: C.CString(privateKeyStr),
|
||||
publicKey: C.CString(publicKeyStr),
|
||||
}
|
||||
}
|
||||
|
||||
//export CreateClient
|
||||
func CreateClient(cUrl *C.char, cPrivateKey *C.char, cChainId C.int, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret *C.char) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = wrapErr(fmt.Errorf("panic: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
url := C.GoString(cUrl)
|
||||
privateKey := C.GoString(cPrivateKey)
|
||||
chainId = uint32(cChainId)
|
||||
apiKeyIndex := uint8(cApiKeyIndex)
|
||||
accountIndex := int64(cAccountIndex)
|
||||
|
||||
httpClient := http.NewClient(url)
|
||||
|
||||
_, err := client.CreateClient(httpClient, privateKey, chainId, apiKeyIndex, accountIndex)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
//export CheckClient
|
||||
func CheckClient(cApiKeyIndex C.int, cAccountIndex C.longlong) (ret *C.char) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = wrapErr(fmt.Errorf("panic: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
return wrapErr(c.Check())
|
||||
}
|
||||
|
||||
//export SignChangePubKey
|
||||
func SignChangePubKey(cPubKey *C.char, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
pubKeyStr := C.GoString(cPubKey)
|
||||
pubKeyBytes, err := hexutil.Decode(pubKeyStr)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
if len(pubKeyBytes) != 40 {
|
||||
return signedTxResponseErr(fmt.Errorf("invalid pub key length. expected 40 but got %v", len(pubKeyBytes)))
|
||||
}
|
||||
var pubKey [40]byte
|
||||
copy(pubKey[:], pubKeyBytes)
|
||||
|
||||
tx := &types.ChangePubKeyReq{
|
||||
PubKey: pubKey,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetChangePubKeyTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCreateOrder
|
||||
func SignCreateOrder(cMarketIndex C.int, cClientOrderIndex C.longlong, cBaseAmount C.longlong, cPrice C.int, cIsAsk C.int, cOrderType C.int, cTimeInForce C.int, cReduceOnly C.int, cTriggerPrice C.int, cOrderExpiry C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
marketIndex := int16(cMarketIndex)
|
||||
clientOrderIndex := int64(cClientOrderIndex)
|
||||
baseAmount := int64(cBaseAmount)
|
||||
price := uint32(cPrice)
|
||||
isAsk := uint8(cIsAsk)
|
||||
orderType := uint8(cOrderType)
|
||||
timeInForce := uint8(cTimeInForce)
|
||||
reduceOnly := uint8(cReduceOnly)
|
||||
triggerPrice := uint32(cTriggerPrice)
|
||||
orderExpiry := int64(cOrderExpiry)
|
||||
|
||||
if orderExpiry == -1 {
|
||||
orderExpiry = time.Now().Add(time.Hour * 24 * 28).UnixMilli() // 28 days
|
||||
}
|
||||
|
||||
tx := &types.CreateOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
ClientOrderIndex: clientOrderIndex,
|
||||
BaseAmount: baseAmount,
|
||||
Price: price,
|
||||
IsAsk: isAsk,
|
||||
Type: orderType,
|
||||
TimeInForce: timeInForce,
|
||||
ReduceOnly: reduceOnly,
|
||||
TriggerPrice: triggerPrice,
|
||||
OrderExpiry: orderExpiry,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCreateOrderTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCreateGroupedOrders
|
||||
func SignCreateGroupedOrders(cGroupingType C.uint8_t, cOrders *C.CreateOrderTxReq, cLen C.int, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
length := int(cLen)
|
||||
orders := make([]*types.CreateOrderTxReq, length)
|
||||
size := unsafe.Sizeof(*cOrders)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
order := (*C.CreateOrderTxReq)(unsafe.Pointer(uintptr(unsafe.Pointer(cOrders)) + uintptr(i)*uintptr(size)))
|
||||
|
||||
orderExpiry := int64(order.OrderExpiry)
|
||||
if orderExpiry == -1 {
|
||||
orderExpiry = time.Now().Add(time.Hour * 24 * 28).UnixMilli()
|
||||
}
|
||||
|
||||
orders[i] = &types.CreateOrderTxReq{
|
||||
MarketIndex: int16(order.MarketIndex),
|
||||
ClientOrderIndex: int64(order.ClientOrderIndex),
|
||||
BaseAmount: int64(order.BaseAmount),
|
||||
Price: uint32(order.Price),
|
||||
IsAsk: uint8(order.IsAsk),
|
||||
Type: uint8(order.Type),
|
||||
TimeInForce: uint8(order.TimeInForce),
|
||||
ReduceOnly: uint8(order.ReduceOnly),
|
||||
TriggerPrice: uint32(order.TriggerPrice),
|
||||
OrderExpiry: orderExpiry,
|
||||
}
|
||||
}
|
||||
|
||||
tx := &types.CreateGroupedOrdersTxReq{
|
||||
GroupingType: uint8(cGroupingType),
|
||||
Orders: orders,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCreateGroupedOrdersTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCancelOrder
|
||||
func SignCancelOrder(cMarketIndex C.int, cOrderIndex C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
marketIndex := int16(cMarketIndex)
|
||||
orderIndex := int64(cOrderIndex)
|
||||
|
||||
tx := &types.CancelOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
Index: orderIndex,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCancelOrderTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignWithdraw
|
||||
func SignWithdraw(cAssetIndex C.int, cRouteType C.int, cAmount C.ulonglong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
assetIndex := int16(cAssetIndex)
|
||||
routeType := uint8(cRouteType)
|
||||
amount := uint64(cAmount)
|
||||
|
||||
tx := &types.WithdrawTxReq{
|
||||
AssetIndex: assetIndex,
|
||||
RouteType: routeType,
|
||||
Amount: amount,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetWithdrawTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCreateSubAccount
|
||||
func SignCreateSubAccount(cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCreateSubAccountTransaction(ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCancelAllOrders
|
||||
func SignCancelAllOrders(cTimeInForce C.int, cTime C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
timeInForce := uint8(cTimeInForce)
|
||||
t := int64(cTime)
|
||||
|
||||
tx := &types.CancelAllOrdersTxReq{
|
||||
TimeInForce: timeInForce,
|
||||
Time: t,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCancelAllOrdersTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignModifyOrder
|
||||
func SignModifyOrder(cMarketIndex C.int, cIndex C.longlong, cBaseAmount C.longlong, cPrice C.longlong, cTriggerPrice C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
marketIndex := int16(cMarketIndex)
|
||||
index := int64(cIndex)
|
||||
baseAmount := int64(cBaseAmount)
|
||||
price := uint32(cPrice)
|
||||
triggerPrice := uint32(cTriggerPrice)
|
||||
|
||||
tx := &types.ModifyOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
Index: index,
|
||||
BaseAmount: baseAmount,
|
||||
Price: price,
|
||||
TriggerPrice: triggerPrice,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetModifyOrderTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignTransfer
|
||||
func SignTransfer(cToAccountIndex C.longlong, cAssetIndex C.int16_t, cFromRouteType, cToRouteType C.uint8_t, cAmount, cUsdcFee C.longlong, cMemo *C.char, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
toAccountIndex := int64(cToAccountIndex)
|
||||
assetIndex := int16(cAssetIndex)
|
||||
fromRouteType := uint8(cFromRouteType)
|
||||
toRouteType := uint8(cToRouteType)
|
||||
amount := int64(cAmount)
|
||||
usdcFee := int64(cUsdcFee)
|
||||
memo := [32]byte{}
|
||||
memoStr := C.GoString(cMemo)
|
||||
if len(memoStr) == 66 {
|
||||
if memoStr[0:2] == "0x" {
|
||||
memoStr = memoStr[2:66]
|
||||
} else {
|
||||
return signedTxResponseErr(fmt.Sprintf("memo expected to be 32 bytes or 64 hex encoded or 66 if 0x hex encoded -- long but received %v", len(memoStr)))
|
||||
}
|
||||
}
|
||||
|
||||
// assume hex encoded here
|
||||
if len(memoStr) == 64 {
|
||||
b, err := hex.DecodeString(memoStr)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(fmt.Sprintf("failed to decode hex string. err: %v", err))
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i += 1 {
|
||||
memo[i] = b[i]
|
||||
}
|
||||
} else if len(memoStr) == 32 {
|
||||
for i := 0; i < 32; i++ {
|
||||
memo[i] = byte(memoStr[i])
|
||||
}
|
||||
} else {
|
||||
return signedTxResponseErr(fmt.Sprintf("memo expected to be 32 bytes or 64 hex encoded or 66 if 0x hex encoded -- long but received %v", len(memoStr)))
|
||||
}
|
||||
|
||||
tx := &types.TransferTxReq{
|
||||
ToAccountIndex: toAccountIndex,
|
||||
AssetIndex: assetIndex,
|
||||
FromRouteType: fromRouteType,
|
||||
ToRouteType: toRouteType,
|
||||
Amount: amount,
|
||||
USDCFee: usdcFee,
|
||||
Memo: memo,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetTransferTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignCreatePublicPool
|
||||
func SignCreatePublicPool(cOperatorFee C.longlong, cInitialTotalShares C.int, cMinOperatorShareRate C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
operatorFee := int64(cOperatorFee)
|
||||
initialTotalShares := int64(cInitialTotalShares)
|
||||
minOperatorShareRate := uint16(cMinOperatorShareRate)
|
||||
|
||||
tx := &types.CreatePublicPoolTxReq{
|
||||
OperatorFee: operatorFee,
|
||||
InitialTotalShares: initialTotalShares,
|
||||
MinOperatorShareRate: minOperatorShareRate,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetCreatePublicPoolTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignUpdatePublicPool
|
||||
func SignUpdatePublicPool(cPublicPoolIndex C.longlong, cStatus C.int, cOperatorFee C.longlong, cMinOperatorShareRate C.int, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := int64(cPublicPoolIndex)
|
||||
status := uint8(cStatus)
|
||||
operatorFee := int64(cOperatorFee)
|
||||
minOperatorShareRate := uint16(cMinOperatorShareRate)
|
||||
|
||||
tx := &types.UpdatePublicPoolTxReq{
|
||||
PublicPoolIndex: publicPoolIndex,
|
||||
Status: status,
|
||||
OperatorFee: operatorFee,
|
||||
MinOperatorShareRate: minOperatorShareRate,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetUpdatePublicPoolTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignMintShares
|
||||
func SignMintShares(cPublicPoolIndex C.longlong, cShareAmount C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := int64(cPublicPoolIndex)
|
||||
shareAmount := int64(cShareAmount)
|
||||
|
||||
tx := &types.MintSharesTxReq{
|
||||
PublicPoolIndex: publicPoolIndex,
|
||||
ShareAmount: shareAmount,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetMintSharesTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignBurnShares
|
||||
func SignBurnShares(cPublicPoolIndex C.longlong, cShareAmount C.longlong, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := int64(cPublicPoolIndex)
|
||||
shareAmount := int64(cShareAmount)
|
||||
|
||||
tx := &types.BurnSharesTxReq{
|
||||
PublicPoolIndex: publicPoolIndex,
|
||||
ShareAmount: shareAmount,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetBurnSharesTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export SignUpdateLeverage
|
||||
func SignUpdateLeverage(cMarketIndex C.int, cInitialMarginFraction C.int, cMarginMode C.int, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
marketIndex := int16(cMarketIndex)
|
||||
initialMarginFraction := uint16(cInitialMarginFraction)
|
||||
marginMode := uint8(cMarginMode)
|
||||
|
||||
tx := &types.UpdateLeverageTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
InitialMarginFraction: initialMarginFraction,
|
||||
MarginMode: marginMode,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetUpdateLeverageTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
//export CreateAuthToken
|
||||
func CreateAuthToken(cDeadline C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.StrOrErr) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = C.StrOrErr{err: wrapErr(fmt.Errorf("panic: %v", r))}
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return C.StrOrErr{err: wrapErr(err)}
|
||||
}
|
||||
|
||||
deadline := int64(cDeadline)
|
||||
if deadline == 0 {
|
||||
deadline = time.Now().Add(time.Hour * 7).Unix()
|
||||
}
|
||||
|
||||
authToken, err := c.GetAuthToken(time.Unix(deadline, 0))
|
||||
if err != nil {
|
||||
return C.StrOrErr{err: wrapErr(err)}
|
||||
}
|
||||
|
||||
return C.StrOrErr{str: C.CString(authToken)}
|
||||
}
|
||||
|
||||
//export SignUpdateMargin
|
||||
func SignUpdateMargin(cMarketIndex C.int, cUSDCAmount C.longlong, cDirection C.int, cNonce C.longlong, cApiKeyIndex C.int, cAccountIndex C.longlong) (ret C.SignedTxResponse) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = signedTxResponsePanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := getClient(cApiKeyIndex, cAccountIndex)
|
||||
if err != nil {
|
||||
return signedTxResponseErr(err)
|
||||
}
|
||||
|
||||
marketIndex := int16(cMarketIndex)
|
||||
usdcAmount := int64(cUSDCAmount)
|
||||
direction := uint8(cDirection)
|
||||
|
||||
tx := &types.UpdateMarginTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
USDCAmount: usdcAmount,
|
||||
Direction: direction,
|
||||
}
|
||||
ops := getTransactOpts(cNonce)
|
||||
|
||||
txInfo, err := c.GetUpdateMarginTransaction(tx, ops)
|
||||
return convertTxInfoToResponse(txInfo, err)
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -0,0 +1,54 @@
|
||||
package signer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
curve "github.com/elliottech/poseidon_crypto/curve/ecgfp5"
|
||||
gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
|
||||
schnorr "github.com/elliottech/poseidon_crypto/signature/schnorr"
|
||||
)
|
||||
|
||||
type Signer interface {
|
||||
Sign(message []byte, hFunc hash.Hash) ([]byte, error)
|
||||
}
|
||||
|
||||
type KeyManager interface {
|
||||
Signer
|
||||
PubKey() gFp5.Element
|
||||
PubKeyBytes() [40]byte
|
||||
PrvKeyBytes() []byte
|
||||
}
|
||||
|
||||
type keyManager struct {
|
||||
key curve.ECgFp5Scalar
|
||||
}
|
||||
|
||||
func NewKeyManager(b []byte) (KeyManager, error) {
|
||||
if len(b) != 40 {
|
||||
return nil, fmt.Errorf("invalid private key length. expected: 40 got: %v", len(b))
|
||||
}
|
||||
return &keyManager{key: curve.ScalarElementFromLittleEndianBytes(b)}, nil
|
||||
}
|
||||
|
||||
func (key *keyManager) Sign(hashedMessage []byte, hFunc hash.Hash) ([]byte, error) {
|
||||
hashedMessageAsQuinticExtension, err := gFp5.FromCanonicalLittleEndianBytes(hashedMessage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse message while signing. message: %v err: %w", hashedMessage, err)
|
||||
}
|
||||
return schnorr.SchnorrSignHashedMessage(hashedMessageAsQuinticExtension, key.key).ToBytes(), nil
|
||||
}
|
||||
|
||||
func (key *keyManager) PubKey() gFp5.Element {
|
||||
return schnorr.SchnorrPkFromSk(key.key)
|
||||
}
|
||||
|
||||
func (key *keyManager) PubKeyBytes() (res [40]byte) {
|
||||
bytes := key.PubKey().ToLittleEndianBytes()
|
||||
copy(res[:], bytes[:])
|
||||
return
|
||||
}
|
||||
|
||||
func (key *keyManager) PrvKeyBytes() []byte {
|
||||
return key.key.ToLittleEndianBytes()
|
||||
}
|
||||
@@ -0,0 +1,670 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/elliottech/lighter-go/signer"
|
||||
"github.com/elliottech/lighter-go/types/txtypes"
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type TransactOpts struct {
|
||||
FromAccountIndex *int64
|
||||
ApiKeyIndex *uint8
|
||||
ExpiredAt int64
|
||||
Nonce *int64
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
type PublicKey = gFp5.Element
|
||||
|
||||
type ChangePubKeyReq struct {
|
||||
PubKey [40]byte
|
||||
}
|
||||
|
||||
type TransferTxReq struct {
|
||||
ToAccountIndex int64
|
||||
AssetIndex int16
|
||||
FromRouteType uint8
|
||||
ToRouteType uint8
|
||||
Amount int64
|
||||
USDCFee int64
|
||||
Memo [32]byte
|
||||
}
|
||||
|
||||
type WithdrawTxReq struct {
|
||||
AssetIndex int16
|
||||
RouteType uint8
|
||||
Amount uint64
|
||||
}
|
||||
|
||||
type CreateOrderTxReq struct {
|
||||
MarketIndex int16
|
||||
ClientOrderIndex int64
|
||||
BaseAmount int64
|
||||
Price uint32
|
||||
IsAsk uint8
|
||||
Type uint8
|
||||
TimeInForce uint8
|
||||
ReduceOnly uint8
|
||||
TriggerPrice uint32
|
||||
OrderExpiry int64
|
||||
}
|
||||
|
||||
type CreateGroupedOrdersTxReq struct {
|
||||
GroupingType uint8
|
||||
Orders []*CreateOrderTxReq
|
||||
}
|
||||
|
||||
type ModifyOrderTxReq struct {
|
||||
MarketIndex int16
|
||||
Index int64
|
||||
BaseAmount int64
|
||||
Price uint32
|
||||
TriggerPrice uint32
|
||||
}
|
||||
|
||||
type CancelOrderTxReq struct {
|
||||
MarketIndex int16
|
||||
Index int64
|
||||
}
|
||||
|
||||
type CancelAllOrdersTxReq struct {
|
||||
TimeInForce uint8
|
||||
Time int64
|
||||
}
|
||||
|
||||
type CreatePublicPoolTxReq struct {
|
||||
OperatorFee int64
|
||||
InitialTotalShares int64
|
||||
MinOperatorShareRate uint16
|
||||
}
|
||||
|
||||
type UpdatePublicPoolTxReq struct {
|
||||
PublicPoolIndex int64
|
||||
Status uint8
|
||||
OperatorFee int64
|
||||
MinOperatorShareRate uint16
|
||||
}
|
||||
|
||||
type MintSharesTxReq struct {
|
||||
PublicPoolIndex int64
|
||||
ShareAmount int64
|
||||
}
|
||||
|
||||
type BurnSharesTxReq struct {
|
||||
PublicPoolIndex int64
|
||||
ShareAmount int64
|
||||
}
|
||||
|
||||
type UpdateLeverageTxReq struct {
|
||||
MarketIndex int16
|
||||
InitialMarginFraction uint16
|
||||
MarginMode uint8
|
||||
}
|
||||
|
||||
type UpdateMarginTxReq struct {
|
||||
MarketIndex int16
|
||||
USDCAmount int64
|
||||
Direction uint8
|
||||
}
|
||||
|
||||
func ConstructAuthToken(key signer.Signer, deadline time.Time, ops *TransactOpts) (string, error) {
|
||||
if ops.FromAccountIndex == nil {
|
||||
return "", fmt.Errorf("missing FromAccountIndex")
|
||||
}
|
||||
if ops.ApiKeyIndex == nil {
|
||||
return "", fmt.Errorf("missing ApiKeyIndex")
|
||||
}
|
||||
message := fmt.Sprintf("%v:%v:%v", deadline.Unix(), *ops.FromAccountIndex, *ops.ApiKeyIndex)
|
||||
|
||||
msgInField, err := g.ArrayFromCanonicalLittleEndianBytes([]byte(message))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert bytes to field element. message: %s, error: %w", message, err)
|
||||
}
|
||||
|
||||
msgHash := p2.HashToQuinticExtension(msgInField).ToLittleEndianBytes()
|
||||
|
||||
signatureBytes, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signature := ethCommon.Bytes2Hex(signatureBytes)
|
||||
|
||||
return fmt.Sprintf("%v:%v", message, signature), err
|
||||
}
|
||||
|
||||
func ConstructChangePubKeyTx(key signer.Signer, lighterChainId uint32, tx *ChangePubKeyReq, ops *TransactOpts) (*txtypes.L2ChangePubKeyTxInfo, error) {
|
||||
convertedTx := ConvertChangePubKeyTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructCreateSubAccountTx(key signer.Signer, lighterChainId uint32, ops *TransactOpts) (*txtypes.L2CreateSubAccountTxInfo, error) {
|
||||
convertedTx := ConvertCreateSubAccountTx(ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructCreatePublicPoolTx(key signer.Signer, lighterChainId uint32, tx *CreatePublicPoolTxReq, ops *TransactOpts) (*txtypes.L2CreatePublicPoolTxInfo, error) {
|
||||
convertedTx := ConvertCreatePublicPoolTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructUpdatePublicPoolTx(key signer.Signer, lighterChainId uint32, tx *UpdatePublicPoolTxReq, ops *TransactOpts) (*txtypes.L2UpdatePublicPoolTxInfo, error) {
|
||||
convertedTx := ConvertUpdatePublicPoolTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructWithdrawTx(key signer.Signer, lighterChainId uint32, tx *WithdrawTxReq, ops *TransactOpts) (*txtypes.L2WithdrawTxInfo, error) {
|
||||
convertedTx := ConvertWithdrawTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructCreateOrderTx(key signer.Signer, lighterChainId uint32, tx *CreateOrderTxReq, ops *TransactOpts) (*txtypes.L2CreateOrderTxInfo, error) {
|
||||
convertedTx := ConvertCreateOrderTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructL2CreateGroupedOrdersTx(key signer.Signer, lighterChainId uint32, tx *CreateGroupedOrdersTxReq, ops *TransactOpts) (*txtypes.L2CreateGroupedOrdersTxInfo, error) {
|
||||
convertedTx := ConvertCreateGroupedOrdersTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructL2CancelOrderTx(key signer.Signer, lighterChainId uint32, tx *CancelOrderTxReq, ops *TransactOpts) (*txtypes.L2CancelOrderTxInfo, error) {
|
||||
convertedTx := ConvertCancelOrderTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructL2ModifyOrderTx(key signer.Signer, lighterChainId uint32, tx *ModifyOrderTxReq, ops *TransactOpts) (*txtypes.L2ModifyOrderTxInfo, error) {
|
||||
convertedTx := ConvertModifyOrderTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructL2CancelAllOrdersTx(key signer.Signer, lighterChainId uint32, tx *CancelAllOrdersTxReq, ops *TransactOpts) (*txtypes.L2CancelAllOrdersTxInfo, error) {
|
||||
convertedTx := ConvertCancelAllOrdersTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructTransferTx(key signer.Signer, lighterChainId uint32, tx *TransferTxReq, ops *TransactOpts) (*txtypes.L2TransferTxInfo, error) {
|
||||
convertedTx := ConvertTransferTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructMintSharesTx(key signer.Signer, lighterChainId uint32, tx *MintSharesTxReq, ops *TransactOpts) (*txtypes.L2MintSharesTxInfo, error) {
|
||||
convertedTx := ConvertMintSharesTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructBurnSharesTx(key signer.Signer, lighterChainId uint32, tx *BurnSharesTxReq, ops *TransactOpts) (*txtypes.L2BurnSharesTxInfo, error) {
|
||||
convertedTx := ConvertBurnSharesTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructUpdateLeverageTx(key signer.Signer, lighterChainId uint32, tx *UpdateLeverageTxReq, ops *TransactOpts) (*txtypes.L2UpdateLeverageTxInfo, error) {
|
||||
convertedTx := ConvertUpdateLeverageTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConstructUpdateMarginTx(key signer.Signer, lighterChainId uint32, tx *UpdateMarginTxReq, ops *TransactOpts) (*txtypes.L2UpdateMarginTxInfo, error) {
|
||||
convertedTx := ConvertUpdateMarginTx(tx, ops)
|
||||
err := convertedTx.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgHash, err := convertedTx.Hash(lighterChainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(msgHash, p2.NewPoseidon2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedTx.SignedHash = ethCommon.Bytes2Hex(msgHash)
|
||||
convertedTx.Sig = signature
|
||||
return convertedTx, nil
|
||||
}
|
||||
|
||||
func ConvertTransferTx(tx *TransferTxReq, ops *TransactOpts) *txtypes.L2TransferTxInfo {
|
||||
return &txtypes.L2TransferTxInfo{
|
||||
FromAccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
ToAccountIndex: tx.ToAccountIndex,
|
||||
AssetIndex: tx.AssetIndex,
|
||||
FromRouteType: tx.FromRouteType,
|
||||
ToRouteType: tx.ToRouteType,
|
||||
Amount: tx.Amount,
|
||||
USDCFee: tx.USDCFee,
|
||||
Memo: tx.Memo,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateOrderTx(tx *CreateOrderTxReq, ops *TransactOpts) *txtypes.L2CreateOrderTxInfo {
|
||||
return &txtypes.L2CreateOrderTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
OrderInfo: &txtypes.OrderInfo{MarketIndex: tx.MarketIndex,
|
||||
ClientOrderIndex: tx.ClientOrderIndex,
|
||||
BaseAmount: tx.BaseAmount,
|
||||
Price: tx.Price,
|
||||
IsAsk: tx.IsAsk,
|
||||
Type: tx.Type,
|
||||
TimeInForce: tx.TimeInForce,
|
||||
ReduceOnly: tx.ReduceOnly,
|
||||
TriggerPrice: tx.TriggerPrice,
|
||||
OrderExpiry: tx.OrderExpiry,
|
||||
},
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateGroupedOrdersTx(tx *CreateGroupedOrdersTxReq, ops *TransactOpts) *txtypes.L2CreateGroupedOrdersTxInfo {
|
||||
ret := &txtypes.L2CreateGroupedOrdersTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
GroupingType: tx.GroupingType,
|
||||
Orders: []*txtypes.OrderInfo{},
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
|
||||
for _, order := range tx.Orders {
|
||||
ret.Orders = append(ret.Orders, &txtypes.OrderInfo{
|
||||
MarketIndex: order.MarketIndex,
|
||||
ClientOrderIndex: order.ClientOrderIndex,
|
||||
BaseAmount: order.BaseAmount,
|
||||
Price: order.Price,
|
||||
IsAsk: order.IsAsk,
|
||||
Type: order.Type,
|
||||
TimeInForce: order.TimeInForce,
|
||||
ReduceOnly: order.ReduceOnly,
|
||||
TriggerPrice: order.TriggerPrice,
|
||||
OrderExpiry: order.OrderExpiry,
|
||||
})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func ConvertCancelOrderTx(tx *CancelOrderTxReq, ops *TransactOpts) *txtypes.L2CancelOrderTxInfo {
|
||||
return &txtypes.L2CancelOrderTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
MarketIndex: tx.MarketIndex,
|
||||
Index: tx.Index,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertModifyOrderTx(tx *ModifyOrderTxReq, ops *TransactOpts) *txtypes.L2ModifyOrderTxInfo {
|
||||
return &txtypes.L2ModifyOrderTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
MarketIndex: tx.MarketIndex,
|
||||
Index: tx.Index,
|
||||
BaseAmount: tx.BaseAmount,
|
||||
Price: tx.Price,
|
||||
TriggerPrice: tx.TriggerPrice,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCancelAllOrdersTx(tx *CancelAllOrdersTxReq, ops *TransactOpts) *txtypes.L2CancelAllOrdersTxInfo {
|
||||
return &txtypes.L2CancelAllOrdersTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
TimeInForce: tx.TimeInForce,
|
||||
Time: tx.Time,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertWithdrawTx(tx *WithdrawTxReq, ops *TransactOpts) *txtypes.L2WithdrawTxInfo {
|
||||
return &txtypes.L2WithdrawTxInfo{
|
||||
FromAccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
AssetIndex: tx.AssetIndex,
|
||||
RouteType: tx.RouteType,
|
||||
Amount: tx.Amount,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertChangePubKeyTx(tx *ChangePubKeyReq, ops *TransactOpts) *txtypes.L2ChangePubKeyTxInfo {
|
||||
return &txtypes.L2ChangePubKeyTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
PubKey: tx.PubKey[:],
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateSubAccountTx(ops *TransactOpts) *txtypes.L2CreateSubAccountTxInfo {
|
||||
return &txtypes.L2CreateSubAccountTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreatePublicPoolTx(tx *CreatePublicPoolTxReq, ops *TransactOpts) *txtypes.L2CreatePublicPoolTxInfo {
|
||||
return &txtypes.L2CreatePublicPoolTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
OperatorFee: tx.OperatorFee,
|
||||
InitialTotalShares: tx.InitialTotalShares,
|
||||
MinOperatorShareRate: tx.MinOperatorShareRate,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUpdatePublicPoolTx(tx *UpdatePublicPoolTxReq, ops *TransactOpts) *txtypes.L2UpdatePublicPoolTxInfo {
|
||||
return &txtypes.L2UpdatePublicPoolTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
PublicPoolIndex: tx.PublicPoolIndex,
|
||||
Status: tx.Status,
|
||||
OperatorFee: tx.OperatorFee,
|
||||
MinOperatorShareRate: tx.MinOperatorShareRate,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertMintSharesTx(tx *MintSharesTxReq, ops *TransactOpts) *txtypes.L2MintSharesTxInfo {
|
||||
return &txtypes.L2MintSharesTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
PublicPoolIndex: tx.PublicPoolIndex,
|
||||
ShareAmount: tx.ShareAmount,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertBurnSharesTx(tx *BurnSharesTxReq, ops *TransactOpts) *txtypes.L2BurnSharesTxInfo {
|
||||
return &txtypes.L2BurnSharesTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
PublicPoolIndex: tx.PublicPoolIndex,
|
||||
ShareAmount: tx.ShareAmount,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUpdateLeverageTx(tx *UpdateLeverageTxReq, ops *TransactOpts) *txtypes.L2UpdateLeverageTxInfo {
|
||||
return &txtypes.L2UpdateLeverageTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
MarketIndex: tx.MarketIndex,
|
||||
InitialMarginFraction: tx.InitialMarginFraction,
|
||||
MarginMode: tx.MarginMode,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUpdateMarginTx(tx *UpdateMarginTxReq, ops *TransactOpts) *txtypes.L2UpdateMarginTxInfo {
|
||||
return &txtypes.L2UpdateMarginTxInfo{
|
||||
AccountIndex: *ops.FromAccountIndex,
|
||||
ApiKeyIndex: *ops.ApiKeyIndex,
|
||||
MarketIndex: tx.MarketIndex,
|
||||
USDCAmount: tx.USDCAmount,
|
||||
Direction: tx.Direction,
|
||||
ExpiredAt: ops.ExpiredAt,
|
||||
Nonce: *ops.Nonce,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2BurnSharesTxInfo)(nil)
|
||||
|
||||
type L2BurnSharesTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
PublicPoolIndex int64
|
||||
ShareAmount int64
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2BurnSharesTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2BurnShares
|
||||
}
|
||||
|
||||
func (txInfo *L2BurnSharesTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2BurnSharesTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2BurnSharesTxInfo) Validate() error {
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// PublicPoolIndex
|
||||
if txInfo.PublicPoolIndex < MinSubAccountIndex {
|
||||
return ErrPublicPoolIndexTooLow
|
||||
}
|
||||
if txInfo.PublicPoolIndex > MaxAccountIndex {
|
||||
return ErrPublicPoolIndexTooHigh
|
||||
}
|
||||
|
||||
if txInfo.ShareAmount < MinPoolSharesToMintOrBurn {
|
||||
return ErrPoolBurnShareAmountTooLow
|
||||
}
|
||||
if txInfo.ShareAmount > MaxPoolSharesToMintOrBurn {
|
||||
return ErrPoolBurnShareAmountTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (txInfo *L2BurnSharesTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 8)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2BurnShares))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.PublicPoolIndex))
|
||||
elems = append(elems, g.FromInt64(txInfo.ShareAmount))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CancelAllOrdersTxInfo)(nil)
|
||||
|
||||
type L2CancelAllOrdersTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
TimeInForce uint8
|
||||
Time int64
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelAllOrdersTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CancelAllOrders
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelAllOrdersTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelAllOrdersTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelAllOrdersTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrAccountIndexTooHigh
|
||||
}
|
||||
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex && txInfo.ApiKeyIndex != NilApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
// TimeInForce and Time
|
||||
switch txInfo.TimeInForce {
|
||||
case ImmediateCancelAll:
|
||||
if txInfo.Time != NilOrderExpiry {
|
||||
return ErrCancelAllTimeisNotNill
|
||||
}
|
||||
case ScheduledCancelAll:
|
||||
if txInfo.Time < MinOrderExpiry || txInfo.Time > MaxOrderExpiry {
|
||||
return ErrCancelAllTimeIsNotInRange
|
||||
}
|
||||
case AbortScheduledCancelAll:
|
||||
if txInfo.Time != 0 {
|
||||
return ErrCancelAllTimeisNotNill
|
||||
}
|
||||
default:
|
||||
return ErrInvalidCancelAllTimeInForce
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelAllOrdersTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 8)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CancelAllOrders))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.TimeInForce)))
|
||||
elems = append(elems, g.FromInt64(txInfo.Time))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CancelOrderTxInfo)(nil)
|
||||
|
||||
type L2CancelOrderTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
MarketIndex int16
|
||||
Index int64 // Client Order Index or Order Index of the order to cancel
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelOrderTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CancelOrder
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelOrderTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelOrderTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelOrderTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// MarketIndex
|
||||
isSpotMarket := txInfo.MarketIndex >= MinSpotMarketIndex && txInfo.MarketIndex <= MaxSpotMarketIndex
|
||||
isPerpsMarket := txInfo.MarketIndex >= MinPerpsMarketIndex && txInfo.MarketIndex <= MaxPerpsMarketIndex
|
||||
if !isSpotMarket && !isPerpsMarket {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
// Index
|
||||
if txInfo.Index < MinClientOrderIndex && txInfo.Index < MinOrderIndex {
|
||||
return ErrOrderIndexTooLow
|
||||
}
|
||||
if txInfo.Index > MaxClientOrderIndex && txInfo.Index > MaxOrderIndex {
|
||||
return ErrOrderIndexTooHigh
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CancelOrderTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 7)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CancelOrder))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MarketIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.Index))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2ChangePubKeyTxInfo)(nil)
|
||||
|
||||
type L2ChangePubKeyTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
PubKey []byte
|
||||
L1Sig string
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2ChangePubKey
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
if !IsValidPubKeyLength(txInfo.PubKey) {
|
||||
return ErrPubKeyInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) GetL1SignatureBody() string {
|
||||
signatureBody := fmt.Sprintf(
|
||||
TemplateChangePubKey,
|
||||
common.Bytes2Hex(txInfo.PubKey),
|
||||
getHex10FromUint64(uint64(txInfo.Nonce)),
|
||||
getHex10FromUint64(uint64(txInfo.AccountIndex)),
|
||||
getHex10FromUint64(uint64(txInfo.ApiKeyIndex)),
|
||||
)
|
||||
return signatureBody
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) GetL1AddressBySignature() common.Address {
|
||||
return calculateL1AddressBySignature(txInfo.GetL1SignatureBody(), txInfo.L1Sig)
|
||||
}
|
||||
|
||||
func (txInfo *L2ChangePubKeyTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 11)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2ChangePubKey))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
|
||||
pubKeyFieldElems, err := g.ArrayFromCanonicalLittleEndianBytes(txInfo.PubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert bytes to field element. bytes: %v, error: %w", txInfo.PubKey, err)
|
||||
}
|
||||
elems = append(elems, pubKeyFieldElems...)
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
curve "github.com/elliottech/poseidon_crypto/curve/ecgfp5"
|
||||
schnorr "github.com/elliottech/poseidon_crypto/signature/schnorr"
|
||||
)
|
||||
|
||||
type (
|
||||
Signature = schnorr.Signature
|
||||
PrivateKey = curve.ECgFp5Scalar
|
||||
)
|
||||
|
||||
const (
|
||||
NilApiKeyIndex = MaxApiKeyIndex + 1
|
||||
)
|
||||
|
||||
const (
|
||||
TxTypeEmpty = 0
|
||||
TxTypeL1Deposit = 1
|
||||
TxTypeL1ChangePubKey = 2
|
||||
TxTypeL1CreateMarket = 3
|
||||
TxTypeL1UpdateMarket = 4
|
||||
TxTypeL1CancelAllOrders = 5
|
||||
TxTypeL1Withdraw = 6
|
||||
TxTypeL1CreateOrder = 7
|
||||
|
||||
TxTypeL2ChangePubKey = 8
|
||||
TxTypeL2CreateSubAccount = 9
|
||||
TxTypeL2CreatePublicPool = 10
|
||||
TxTypeL2UpdatePublicPool = 11
|
||||
TxTypeL2Transfer = 12
|
||||
TxTypeL2Withdraw = 13
|
||||
TxTypeL2CreateOrder = 14
|
||||
TxTypeL2CancelOrder = 15
|
||||
TxTypeL2CancelAllOrders = 16
|
||||
TxTypeL2ModifyOrder = 17
|
||||
TxTypeL2MintShares = 18
|
||||
TxTypeL2BurnShares = 19
|
||||
TxTypeL2UpdateLeverage = 20
|
||||
|
||||
TxTypeInternalClaimOrder = 21
|
||||
TxTypeInternalCancelOrder = 22
|
||||
TxTypeInternalDeleverage = 23
|
||||
TxTypeInternalExitPosition = 24
|
||||
TxTypeInternalCancelAllOrders = 25
|
||||
TxTypeInternalLiquidatePosition = 26
|
||||
TxTypeInternalCreateOrder = 27
|
||||
|
||||
TxTypeL2CreateGroupedOrders = 28
|
||||
TxTypeL2UpdateMargin = 29
|
||||
TxTypeL1BurnShares = 30
|
||||
)
|
||||
|
||||
// Order Type
|
||||
const (
|
||||
// User set order types
|
||||
LimitOrder = iota
|
||||
MarketOrder = 1
|
||||
StopLossOrder = 2
|
||||
StopLossLimitOrder = 3
|
||||
TakeProfitOrder = 4
|
||||
TakeProfitLimitOrder = 5
|
||||
TWAPOrder = 6
|
||||
|
||||
// Internal order types
|
||||
TWAPSubOrder = 7
|
||||
LiquidationOrder = 8
|
||||
|
||||
ApiMaxOrderType = TWAPOrder
|
||||
)
|
||||
|
||||
// Order Time-In-Force
|
||||
const (
|
||||
ImmediateOrCancel = iota
|
||||
GoodTillTime = 1
|
||||
PostOnly = 2
|
||||
)
|
||||
|
||||
// Grouping Type
|
||||
const (
|
||||
GroupingType = 0
|
||||
GroupingType_OneTriggersTheOther = 1
|
||||
GroupingType_OneCancelsTheOther = 2
|
||||
GroupingType_OneTriggersAOneCancelsTheOther = 3
|
||||
)
|
||||
|
||||
// Cancel All Orders Time-In-Force
|
||||
const (
|
||||
ImmediateCancelAll = iota
|
||||
ScheduledCancelAll = 1
|
||||
AbortScheduledCancelAll = 2
|
||||
)
|
||||
|
||||
// Asset Margin Mode
|
||||
const (
|
||||
AssetMarginMode_Disabled = 0
|
||||
AssetMarginMode_Enabled = 1
|
||||
AssetMarginMode_Max = AssetMarginMode_Enabled
|
||||
)
|
||||
|
||||
// Asset Route Type
|
||||
const (
|
||||
AssetRouteType_Perps = 0
|
||||
AssetRouteType_Spot = 1
|
||||
)
|
||||
|
||||
// Position Margin Mode
|
||||
const (
|
||||
CrossMargin = iota
|
||||
IsolatedMargin = 1
|
||||
)
|
||||
|
||||
// Margin Update Direction
|
||||
const (
|
||||
RemoveFromIsolatedMargin = iota
|
||||
AddToIsolatedMargin = 1
|
||||
)
|
||||
|
||||
const (
|
||||
OneUSDC = 1000000
|
||||
|
||||
FeeTick int64 = 1_000_000
|
||||
MarginFractionTick int64 = 10_000
|
||||
ShareTick uint16 = 10_000
|
||||
|
||||
MinAccountIndex int64 = 0
|
||||
MaxAccountIndex int64 = 281474976710654 // (1 << 48) - 2
|
||||
MaxMasterAccountIndex int64 = 140737488355327 // (1 << 47) - 1
|
||||
MinSubAccountIndex int64 = 140737488355328 // (1 << 47)
|
||||
MinApiKeyIndex uint8 = 0
|
||||
MaxApiKeyIndex uint8 = 254 // (1 << 8) - 2
|
||||
|
||||
MinMarketIndex int16 = 0
|
||||
MinPerpsMarketIndex int16 = 0
|
||||
MaxPerpsMarketIndex int16 = 254 // (1 << 8) - 2
|
||||
NilMarketIndex int16 = 255
|
||||
MinSpotMarketIndex int16 = 2048 // (1 << 11)
|
||||
MaxSpotMarketIndex int16 = 4094 // (1 << 12) - 2
|
||||
|
||||
NativeAssetIndex = uint16(1)
|
||||
USDCAssetIndex = uint16(3)
|
||||
MinAssetIndex = 1
|
||||
MaxAssetIndex = (1 << 6) - 2
|
||||
NilAssetIndex = 0
|
||||
|
||||
MaxInvestedPublicPoolCount int64 = 16
|
||||
InitialPoolShareValue int64 = 1_000 // 0.001 USDC
|
||||
MinInitialTotalShares int64 = 1_000 * (OneUSDC / InitialPoolShareValue) // 1,000 USDC worth of shares
|
||||
MaxInitialTotalShares int64 = 1_000_000_000 * (OneUSDC / InitialPoolShareValue) // 1,000,000,000 USDC worth of shares
|
||||
MaxPoolShares int64 = (1 << 60) - 1
|
||||
MaxBurntShareUSDCValue int64 = (1 << 60) - 1
|
||||
|
||||
MaxPoolEntryUSDC = (1 << 56) - 1 // 2^56 - 1 max USDC to invest in a pool
|
||||
MinPoolSharesToMintOrBurn int64 = 1
|
||||
MaxPoolSharesToMintOrBurn int64 = (1 << 60) - 1
|
||||
|
||||
MinNonce int64 = 0
|
||||
|
||||
MinOrderNonce int64 = 0
|
||||
MaxOrderNonce int64 = (1 << 48) - 1
|
||||
|
||||
NilClientOrderIndex int64 = 0
|
||||
NilOrderIndex int64 = 0
|
||||
|
||||
MinClientOrderIndex int64 = 1
|
||||
MaxClientOrderIndex int64 = (1 << 48) - 1
|
||||
|
||||
MinOrderIndex int64 = MaxClientOrderIndex + 1
|
||||
MaxOrderIndex int64 = (1 << 60) - 1
|
||||
|
||||
MinOrderBaseAmount int64 = 1
|
||||
MaxOrderBaseAmount int64 = (1 << 48) - 1
|
||||
NilOrderBaseAmount int64 = 0
|
||||
|
||||
NilOrderPrice uint32 = 0
|
||||
MinOrderPrice uint32 = 1
|
||||
MaxOrderPrice uint32 = (1 << 32) - 1
|
||||
|
||||
MinOrderCancelAllPeriod int64 = 1000 * 60 * 5 // 5 minutes
|
||||
MaxOrderCancelAllPeriod int64 = 1000 * 60 * 60 * 24 * 15 // 15 days
|
||||
|
||||
NilOrderExpiry int64 = 0
|
||||
MinOrderExpiry int64 = 1
|
||||
MaxOrderExpiry int64 = math.MaxInt64
|
||||
|
||||
MinOrderExpiryPeriod int64 = 1000 * 60 * 5 // 5 minutes
|
||||
MaxOrderExpiryPeriod int64 = 1000 * 60 * 60 * 24 * 30 // 30 days
|
||||
|
||||
NilOrderTriggerPrice uint32 = 0
|
||||
MinOrderTriggerPrice uint32 = 1
|
||||
MaxOrderTriggerPrice uint32 = (1 << 32) - 1
|
||||
|
||||
MaxGroupedOrderCount int64 = 3
|
||||
|
||||
MaxTimestamp = (1 << 48) - 1
|
||||
)
|
||||
|
||||
const (
|
||||
MaxExchangeUSDC = (1 << 60) - 1
|
||||
|
||||
MinTransferAmount int64 = 1
|
||||
MaxTransferAmount int64 = MaxExchangeUSDC
|
||||
|
||||
MinWithdrawalAmount uint64 = 1
|
||||
MaxWithdrawalAmount uint64 = MaxExchangeUSDC
|
||||
)
|
||||
@@ -0,0 +1,333 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CreateGroupedOrdersTxInfo)(nil)
|
||||
|
||||
// !!! Ensure that if primary order is reduce only, all child orders are also reduce only
|
||||
// !!! Otherwise CancelPositionTiedAccountOrders flow breaks
|
||||
type L2CreateGroupedOrdersTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
GroupingType uint8
|
||||
|
||||
Orders []*OrderInfo
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CreateGroupedOrders
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrAccountIndexTooHigh
|
||||
}
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
if len(txInfo.Orders) == 0 || len(txInfo.Orders) > int(MaxGroupedOrderCount) {
|
||||
return ErrOrderGroupSizeInvalid
|
||||
}
|
||||
|
||||
// MarketIndex for first order
|
||||
if txInfo.Orders[0].MarketIndex < MinPerpsMarketIndex || txInfo.Orders[0].MarketIndex > MaxPerpsMarketIndex {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
// Perform range checks for all orders
|
||||
for _, order := range txInfo.Orders {
|
||||
// MarketIndex
|
||||
if order.MarketIndex != txInfo.Orders[0].MarketIndex {
|
||||
return ErrMarketIndexMismatch
|
||||
}
|
||||
|
||||
// ClientOrderIndex
|
||||
if order.ClientOrderIndex != NilClientOrderIndex {
|
||||
return ErrClientOrderIndexNotNil
|
||||
}
|
||||
|
||||
// BaseAmount
|
||||
if order.ReduceOnly != 1 && order.BaseAmount == NilOrderBaseAmount {
|
||||
return ErrBaseAmountTooLow
|
||||
}
|
||||
if order.BaseAmount != NilOrderBaseAmount && order.BaseAmount < MinOrderBaseAmount {
|
||||
return ErrBaseAmountTooLow
|
||||
}
|
||||
if order.BaseAmount > MaxOrderBaseAmount {
|
||||
return ErrBaseAmountTooHigh
|
||||
}
|
||||
|
||||
// Price
|
||||
if order.Price < MinOrderPrice {
|
||||
return ErrPriceTooLow
|
||||
}
|
||||
if order.Price > MaxOrderPrice {
|
||||
return ErrPriceTooHigh
|
||||
}
|
||||
|
||||
// IsAsk
|
||||
if order.IsAsk != 0 && order.IsAsk != 1 {
|
||||
return ErrIsAskInvalid
|
||||
}
|
||||
|
||||
// TimeInForce
|
||||
if order.TimeInForce != ImmediateOrCancel && order.TimeInForce != GoodTillTime && order.TimeInForce != PostOnly {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
}
|
||||
|
||||
// ReduceOnly
|
||||
if order.ReduceOnly != 0 && order.ReduceOnly != 1 {
|
||||
return ErrOrderReduceOnlyInvalid
|
||||
}
|
||||
|
||||
// OrderExpiry
|
||||
if (order.OrderExpiry < MinOrderExpiry || order.OrderExpiry > MaxOrderExpiry) && order.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
// TriggerPrice
|
||||
if (order.TriggerPrice < MinOrderTriggerPrice || order.TriggerPrice > MaxOrderTriggerPrice) && order.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
switch txInfo.GroupingType {
|
||||
case GroupingType_OneCancelsTheOther:
|
||||
return txInfo.ValidateOCO()
|
||||
case GroupingType_OneTriggersTheOther:
|
||||
return txInfo.ValidateOTO()
|
||||
case GroupingType_OneTriggersAOneCancelsTheOther:
|
||||
return txInfo.ValidateOTOCO()
|
||||
default:
|
||||
return ErrGroupingTypeInvalid
|
||||
}
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateParentOrder(order *OrderInfo) error {
|
||||
switch order.Type {
|
||||
case MarketOrder:
|
||||
if order.TimeInForce != ImmediateOrCancel {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
} else if order.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
} else if order.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
}
|
||||
case LimitOrder:
|
||||
if order.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if order.TimeInForce == ImmediateOrCancel && order.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
} else if order.TimeInForce != ImmediateOrCancel && order.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
default:
|
||||
return ErrOrderTypeInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateChildOrder(order *OrderInfo) error {
|
||||
switch order.Type {
|
||||
case StopLossOrder, TakeProfitOrder:
|
||||
if order.TimeInForce != ImmediateOrCancel {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
} else if order.TriggerPrice == NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if order.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
case StopLossLimitOrder, TakeProfitLimitOrder:
|
||||
if order.TriggerPrice == NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if order.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
default:
|
||||
return ErrOrderTypeInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateSiblingOrders(orders []*OrderInfo) error {
|
||||
if len(orders) != 2 {
|
||||
return ErrOrderGroupSizeInvalid
|
||||
}
|
||||
slFlag := false
|
||||
tpFlag := false
|
||||
for _, order := range orders {
|
||||
err := txInfo.ValidateChildOrder(order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Type == StopLossOrder || order.Type == StopLossLimitOrder {
|
||||
slFlag = true
|
||||
} else if order.Type == TakeProfitOrder || order.Type == TakeProfitLimitOrder {
|
||||
tpFlag = true
|
||||
}
|
||||
}
|
||||
if !slFlag || !tpFlag {
|
||||
return ErrOrderTypeInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateOCO() error {
|
||||
if len(txInfo.Orders) != 2 {
|
||||
return ErrOrderGroupSizeInvalid
|
||||
}
|
||||
|
||||
// Ensure both orders base sizes are same
|
||||
if txInfo.Orders[0].BaseAmount != txInfo.Orders[1].BaseAmount {
|
||||
return ErrBaseAmountsNotEqual
|
||||
}
|
||||
|
||||
// Orders should be in the same direction
|
||||
if txInfo.Orders[0].IsAsk != txInfo.Orders[1].IsAsk {
|
||||
return ErrIsAskInvalid
|
||||
}
|
||||
|
||||
// Ensure both orders are reduce only
|
||||
if txInfo.Orders[0].ReduceOnly != 1 || txInfo.Orders[1].ReduceOnly != 1 {
|
||||
return ErrOrderReduceOnlyInvalid
|
||||
}
|
||||
|
||||
// Ensure both orders have the same non-nil expiry
|
||||
if txInfo.Orders[0].OrderExpiry != txInfo.Orders[1].OrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
return txInfo.ValidateSiblingOrders(txInfo.Orders)
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateOTO() error {
|
||||
if len(txInfo.Orders) != 2 {
|
||||
return ErrOrderGroupSizeInvalid
|
||||
}
|
||||
|
||||
// Ensure child order base size is 0
|
||||
if txInfo.Orders[1].BaseAmount != NilOrderBaseAmount {
|
||||
return ErrBaseAmountNotNil
|
||||
}
|
||||
|
||||
// Orders should be in the opposite direction
|
||||
if txInfo.Orders[0].IsAsk == txInfo.Orders[1].IsAsk {
|
||||
return ErrIsAskInvalid
|
||||
}
|
||||
|
||||
// Ensure if expiries are not nil, they are the same
|
||||
if txInfo.Orders[0].OrderExpiry != NilOrderExpiry &&
|
||||
txInfo.Orders[0].OrderExpiry != txInfo.Orders[1].OrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
err := txInfo.ValidateParentOrder(txInfo.Orders[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txInfo.ValidateChildOrder(txInfo.Orders[1])
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) ValidateOTOCO() error {
|
||||
if len(txInfo.Orders) != 3 {
|
||||
return ErrOrderGroupSizeInvalid
|
||||
}
|
||||
|
||||
// Ensure child orders base size is 0
|
||||
if txInfo.Orders[1].BaseAmount != NilOrderBaseAmount || txInfo.Orders[2].BaseAmount != NilOrderBaseAmount {
|
||||
return ErrBaseAmountNotNil
|
||||
}
|
||||
|
||||
// Primary and child orders should be in the oppsite direction
|
||||
if txInfo.Orders[0].IsAsk == txInfo.Orders[1].IsAsk || txInfo.Orders[0].IsAsk == txInfo.Orders[2].IsAsk {
|
||||
return ErrIsAskInvalid
|
||||
}
|
||||
|
||||
// Ensure child orders has the same expiry
|
||||
if txInfo.Orders[1].OrderExpiry != txInfo.Orders[2].OrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
// Ensure if expiries are not nil, they are the same
|
||||
if txInfo.Orders[0].OrderExpiry != NilOrderExpiry &&
|
||||
txInfo.Orders[0].OrderExpiry != txInfo.Orders[1].OrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
err := txInfo.ValidateParentOrder(txInfo.Orders[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txInfo.ValidateSiblingOrders(txInfo.Orders[1:])
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateGroupedOrdersTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 11)
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CreateGroupedOrders))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.GroupingType)))
|
||||
|
||||
aggregatedOrderHash := p2.EmptyHashOut()
|
||||
for index, order := range txInfo.Orders {
|
||||
orderHash := p2.HashNoPad([]g.Element{
|
||||
g.FromUint32(uint32(order.MarketIndex)),
|
||||
g.FromInt64(order.ClientOrderIndex),
|
||||
g.FromInt64(order.BaseAmount),
|
||||
g.FromUint32(order.Price),
|
||||
g.FromUint32(uint32(order.IsAsk)),
|
||||
g.FromUint32(uint32(order.Type)),
|
||||
g.FromUint32(uint32(order.TimeInForce)),
|
||||
g.FromUint32(uint32(order.ReduceOnly)),
|
||||
g.FromUint32(order.TriggerPrice),
|
||||
g.FromInt64(order.OrderExpiry),
|
||||
})
|
||||
if index == 0 {
|
||||
aggregatedOrderHash = orderHash
|
||||
} else {
|
||||
aggregatedOrderHash = p2.HashNToOne([]p2.HashOut{aggregatedOrderHash, orderHash})
|
||||
}
|
||||
}
|
||||
elems = append(elems, aggregatedOrderHash[:]...)
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CreateOrderTxInfo)(nil)
|
||||
|
||||
type L2CreateOrderTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
*OrderInfo
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateOrderTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CreateOrder
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateOrderTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateOrderTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateOrderTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrAccountIndexTooHigh
|
||||
}
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// MarketIndex
|
||||
isSpotMarket := txInfo.MarketIndex >= MinSpotMarketIndex && txInfo.MarketIndex <= MaxSpotMarketIndex
|
||||
isPerpsMarket := txInfo.MarketIndex >= MinPerpsMarketIndex && txInfo.MarketIndex <= MaxPerpsMarketIndex
|
||||
if !isSpotMarket && !isPerpsMarket {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
// ClientOrderIndex
|
||||
if txInfo.ClientOrderIndex != NilClientOrderIndex {
|
||||
if txInfo.ClientOrderIndex < MinClientOrderIndex {
|
||||
return ErrClientOrderIndexTooLow
|
||||
}
|
||||
if txInfo.ClientOrderIndex > MaxClientOrderIndex {
|
||||
return ErrClientOrderIndexTooHigh
|
||||
}
|
||||
}
|
||||
|
||||
// BaseAmount
|
||||
if txInfo.ReduceOnly != 1 && txInfo.BaseAmount == NilOrderBaseAmount {
|
||||
return ErrBaseAmountTooLow
|
||||
}
|
||||
if txInfo.BaseAmount != NilOrderBaseAmount && txInfo.BaseAmount < MinOrderBaseAmount {
|
||||
return ErrBaseAmountTooLow
|
||||
}
|
||||
if txInfo.BaseAmount > MaxOrderBaseAmount {
|
||||
return ErrBaseAmountTooHigh
|
||||
}
|
||||
|
||||
// Price
|
||||
if txInfo.Price < MinOrderPrice {
|
||||
return ErrPriceTooLow
|
||||
}
|
||||
if txInfo.Price > MaxOrderPrice {
|
||||
return ErrPriceTooHigh
|
||||
}
|
||||
|
||||
// IsAsk
|
||||
if txInfo.IsAsk != 0 && txInfo.IsAsk != 1 {
|
||||
return ErrIsAskInvalid
|
||||
}
|
||||
|
||||
// Type
|
||||
if txInfo.TimeInForce != ImmediateOrCancel && txInfo.TimeInForce != GoodTillTime && txInfo.TimeInForce != PostOnly {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
}
|
||||
|
||||
// ReduceOnly
|
||||
if (txInfo.ReduceOnly != 0 && txInfo.ReduceOnly != 1) || (isSpotMarket && txInfo.ReduceOnly == 1) {
|
||||
return ErrOrderReduceOnlyInvalid
|
||||
}
|
||||
|
||||
// OrderExpiry
|
||||
if (txInfo.OrderExpiry < MinOrderExpiry || txInfo.OrderExpiry > MaxOrderExpiry) && txInfo.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
|
||||
switch txInfo.Type {
|
||||
case MarketOrder:
|
||||
if txInfo.TimeInForce != ImmediateOrCancel {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
} else if txInfo.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
} else if txInfo.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
}
|
||||
case LimitOrder:
|
||||
if txInfo.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if txInfo.TimeInForce == ImmediateOrCancel && txInfo.OrderExpiry != NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
} else if txInfo.TimeInForce != ImmediateOrCancel && txInfo.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
case StopLossOrder, TakeProfitOrder:
|
||||
if !isPerpsMarket {
|
||||
return ErrOrderTypeInvalid
|
||||
} else if txInfo.TimeInForce != ImmediateOrCancel {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
} else if txInfo.TriggerPrice == NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if txInfo.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
case StopLossLimitOrder, TakeProfitLimitOrder:
|
||||
if !isPerpsMarket {
|
||||
return ErrOrderTypeInvalid
|
||||
} else if txInfo.TriggerPrice == NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if txInfo.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
case TWAPOrder:
|
||||
if txInfo.TimeInForce != GoodTillTime {
|
||||
return ErrOrderTimeInForceInvalid
|
||||
} else if txInfo.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
} else if txInfo.OrderExpiry == NilOrderExpiry {
|
||||
return ErrOrderExpiryInvalid
|
||||
}
|
||||
default:
|
||||
return ErrOrderTypeInvalid
|
||||
}
|
||||
|
||||
// TriggerPrice
|
||||
if (txInfo.TriggerPrice < MinOrderTriggerPrice || txInfo.TriggerPrice > MaxOrderTriggerPrice) && txInfo.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateOrderTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 16)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CreateOrder))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MarketIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.ClientOrderIndex))
|
||||
elems = append(elems, g.FromInt64(txInfo.BaseAmount))
|
||||
elems = append(elems, g.FromUint32(txInfo.Price))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.IsAsk)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.Type)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.TimeInForce)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ReduceOnly)))
|
||||
elems = append(elems, g.FromUint32(txInfo.TriggerPrice))
|
||||
elems = append(elems, g.FromInt64(txInfo.OrderExpiry))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CreatePublicPoolTxInfo)(nil)
|
||||
|
||||
type L2CreatePublicPoolTxInfo struct {
|
||||
AccountIndex int64 // Master account index
|
||||
ApiKeyIndex uint8
|
||||
|
||||
OperatorFee int64
|
||||
InitialTotalShares int64
|
||||
MinOperatorShareRate uint16
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CreatePublicPoolTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CreatePublicPool
|
||||
}
|
||||
|
||||
func (txInfo *L2CreatePublicPoolTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CreatePublicPoolTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CreatePublicPoolTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxMasterAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// OperatorFee
|
||||
if txInfo.OperatorFee < 0 || txInfo.OperatorFee > FeeTick {
|
||||
return ErrInvalidPoolOperatorFee
|
||||
}
|
||||
|
||||
// InitialTotalShares
|
||||
if txInfo.InitialTotalShares <= 0 {
|
||||
return ErrPoolInitialTotalSharesTooLow
|
||||
}
|
||||
if txInfo.InitialTotalShares > MaxInitialTotalShares {
|
||||
return ErrPoolInitialTotalSharesTooHigh
|
||||
}
|
||||
|
||||
// MinOperatorShareRate
|
||||
if txInfo.MinOperatorShareRate > ShareTick {
|
||||
return ErrPoolMinOperatorShareRateTooHigh
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreatePublicPoolTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 9)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CreatePublicPool))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.OperatorFee))
|
||||
elems = append(elems, g.FromInt64(txInfo.InitialTotalShares))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MinOperatorShareRate)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2CreateSubAccountTxInfo)(nil)
|
||||
|
||||
type L2CreateSubAccountTxInfo struct {
|
||||
AccountIndex int64 // Master account index
|
||||
ApiKeyIndex uint8
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateSubAccountTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2CreateSubAccount
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateSubAccountTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateSubAccountTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateSubAccountTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxMasterAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2CreateSubAccountTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 6)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2CreateSubAccount))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package txtypes
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
ErrAssetIndexTooLow = fmt.Errorf("AssetIndex should not be less than %d", MinAssetIndex)
|
||||
ErrAssetIndexTooHigh = fmt.Errorf("AssetIndex should not be larger than %d", MaxAssetIndex)
|
||||
ErrRouteTypeInvalid = fmt.Errorf("RouteType is invalid")
|
||||
ErrAccountIndexTooLow = fmt.Errorf("AccountIndex should not be less than %d", MinAccountIndex)
|
||||
ErrAccountIndexTooHigh = fmt.Errorf("AccountIndex should not be larger than %d", MaxAccountIndex)
|
||||
ErrNonceTooLow = fmt.Errorf("AccountNonce should not be less than %d", MinNonce)
|
||||
ErrInvalidCancelAllTimeInForce = fmt.Errorf("CancelAllTimeInForce is invalid")
|
||||
ErrOrderReduceOnlyInvalid = fmt.Errorf("ReduceOnly is invalid")
|
||||
ErrOrderTriggerPriceInvalid = fmt.Errorf("TriggerPrice is invalid")
|
||||
ErrOrderExpiryInvalid = fmt.Errorf("OrderExpiry is invalid")
|
||||
ErrExpiredAtInvalid = fmt.Errorf("ExpiredAt is invalid")
|
||||
ErrCancelAllTimeIsNotInRange = fmt.Errorf("CancelAllTime should be larger than 0 and not larger than %d", MaxOrderExpiry)
|
||||
ErrCancelAllTimeisNotNill = fmt.Errorf("CancelAllTime should be nil")
|
||||
ErrPubKeyInvalid = fmt.Errorf("PubKey is invalid")
|
||||
ErrToAccountIndexTooLow = fmt.Errorf("ToAccountIndex should not be less than %d", MinAccountIndex)
|
||||
ErrToAccountIndexTooHigh = fmt.Errorf("ToAccountIndex should not be larger than %d", MaxAccountIndex)
|
||||
ErrFromAccountIndexTooLow = fmt.Errorf("FromAccountIndex should not be less than %d", MinAccountIndex)
|
||||
ErrFromAccountIndexTooHigh = fmt.Errorf("FromAccountIndex should not be larger than %d", MaxAccountIndex)
|
||||
ErrApiKeyIndexTooLow = fmt.Errorf("ApiKeyIndex should not be less than %d", MinApiKeyIndex)
|
||||
ErrApiKeyIndexTooHigh = fmt.Errorf("ApiKeyIndex should not be larger than %d", MaxApiKeyIndex)
|
||||
ErrPublicPoolIndexTooLow = fmt.Errorf("PublicPoolIndex should not be less than %d", MinAccountIndex)
|
||||
ErrPublicPoolIndexTooHigh = fmt.Errorf("PublicPoolIndex should not be larger than %d", MaxAccountIndex)
|
||||
ErrInvalidPoolOperatorFee = fmt.Errorf("PoolOperatorFee should be larger than 0 and not larger than %d", FeeTick)
|
||||
ErrInvalidPoolStatus = fmt.Errorf("PoolStatus should be either 0 or 1")
|
||||
ErrPoolInitialTotalSharesTooLow = fmt.Errorf("PoolInitialTotalShares should be larger than %d", MinInitialTotalShares)
|
||||
ErrPoolInitialTotalSharesTooHigh = fmt.Errorf("PoolInitialTotalShares should not be larger than %d", MaxInitialTotalShares)
|
||||
ErrPoolMinOperatorShareRateTooLow = fmt.Errorf("PoolMinOperatorShareRate should be larger than 0")
|
||||
ErrPoolMinOperatorShareRateTooHigh = fmt.Errorf("PoolMinOperatorShareRate should not be larger than %d", ShareTick)
|
||||
ErrPoolMintShareAmountTooLow = fmt.Errorf("PoolMintShareAmount should be larger than %d", MinPoolSharesToMintOrBurn)
|
||||
ErrPoolMintShareAmountTooHigh = fmt.Errorf("PoolMintShareAmount should not be larger than %d", MaxPoolSharesToMintOrBurn)
|
||||
ErrPoolBurnShareAmountTooLow = fmt.Errorf("PoolBurnShareAmount should be larger than %d", MinPoolSharesToMintOrBurn)
|
||||
ErrPoolBurnShareAmountTooHigh = fmt.Errorf("PoolBurnShareAmount should not be larger than %d", MaxPoolSharesToMintOrBurn)
|
||||
ErrWithdrawalAmountTooLow = fmt.Errorf("WithdrawalAmount should be larger than %d", MinWithdrawalAmount)
|
||||
ErrWithdrawalAmountTooHigh = fmt.Errorf("WithdrawalAmount should not be larger than %d", MaxWithdrawalAmount)
|
||||
ErrTransferAmountTooLow = fmt.Errorf("TransferAmount should be larger than %d", MinTransferAmount)
|
||||
ErrTransferAmountTooHigh = fmt.Errorf("TransferAmount should not be larger than %d", MaxTransferAmount)
|
||||
ErrTransferFeeNegative = fmt.Errorf("TransferFee should not be negative")
|
||||
ErrTransferFeeTooHigh = fmt.Errorf("TransferFee should not be larger than %d", MaxTransferAmount)
|
||||
ErrMarketIndexTooLow = fmt.Errorf("MarketIndex should not be less than %d", MinMarketIndex)
|
||||
ErrMarketIndexTooHigh = fmt.Errorf("MarketIndex should not be larger than %d", MaxSpotMarketIndex)
|
||||
ErrMarketIndexMismatch = fmt.Errorf("MarketIndex should match the market index of the order")
|
||||
ErrInvalidMarketIndex = fmt.Errorf("MarketIndex is not valid")
|
||||
ErrInitialMarginFractionTooLow = fmt.Errorf("InitialMarginFraction should not be less than %d", 0)
|
||||
ErrInitialMarginFractionTooHigh = fmt.Errorf("InitialMarginFraction should not be larger than %d", MarginFractionTick)
|
||||
ErrClientOrderIndexTooLow = fmt.Errorf("ClientOrderIndex should not be less than %d", MinClientOrderIndex)
|
||||
ErrClientOrderIndexTooHigh = fmt.Errorf("ClientOrderIndex should not be larger than %d", MaxClientOrderIndex)
|
||||
ErrClientOrderIndexNotNil = fmt.Errorf("ClientOrderIndex should be nil")
|
||||
ErrOrderIndexTooLow = fmt.Errorf("OrderIndex should not be less than %d", MinOrderIndex)
|
||||
ErrOrderIndexTooHigh = fmt.Errorf("OrderIndex should not be larger than %d", MaxOrderIndex)
|
||||
ErrBaseAmountTooLow = fmt.Errorf("BaseAmount should not be less than %d", MinOrderBaseAmount)
|
||||
ErrBaseAmountTooHigh = fmt.Errorf("BaseAmount should not be larger than %d", MaxOrderBaseAmount)
|
||||
ErrBaseAmountsNotEqual = fmt.Errorf("BaseAmounts should be equal")
|
||||
ErrBaseAmountNotNil = fmt.Errorf("BaseAmount should be nil")
|
||||
ErrPriceTooLow = fmt.Errorf("OrderPrice should not be less than %d", MinOrderPrice)
|
||||
ErrPriceTooHigh = fmt.Errorf("OrderPrice should not be larger than %d", MaxOrderPrice)
|
||||
ErrIsAskInvalid = fmt.Errorf("IsAsk should be 0 or 1")
|
||||
ErrOrderTypeInvalid = fmt.Errorf("OrderType is not valid")
|
||||
ErrOrderTimeInForceInvalid = fmt.Errorf("OrderTimeInForce is not valid")
|
||||
ErrGroupingTypeInvalid = fmt.Errorf("GroupingType is not valid")
|
||||
ErrOrderGroupSizeInvalid = fmt.Errorf("OrderGroupSize is not valid")
|
||||
ErrInvalidSignature = fmt.Errorf("TxSignature is invalid")
|
||||
ErrInvalidMarginMode = fmt.Errorf("MarginMode is not valid")
|
||||
ErrCancelModeInvalid = fmt.Errorf("CancelMode is not valid")
|
||||
ErrInvalidUpdateMarginDirection = fmt.Errorf("Margin movement direction is not valid")
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
package txtypes
|
||||
|
||||
import g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
|
||||
type TxInfo interface {
|
||||
GetTxType() uint8
|
||||
|
||||
GetTxInfo() (string, error)
|
||||
|
||||
// GetTxHash returns the hash that was signed when creating this transaction.
|
||||
// The hash coincides with the TxHash received from Lighter after submitting this Tx.
|
||||
// It can be used to get the TxHash in advance, or to double-check the correctness of the SDK.
|
||||
// As this hash is signed by the ApiKey, if the value differs than the one computed by the server,
|
||||
// it'll result in an invalid signature.
|
||||
// Returns empty string if the Tx is not signed.
|
||||
GetTxHash() string
|
||||
|
||||
Validate() error
|
||||
|
||||
Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error)
|
||||
}
|
||||
|
||||
type OrderInfo struct {
|
||||
MarketIndex int16
|
||||
|
||||
ClientOrderIndex int64
|
||||
|
||||
BaseAmount int64
|
||||
Price uint32
|
||||
IsAsk uint8
|
||||
|
||||
Type uint8
|
||||
TimeInForce uint8
|
||||
ReduceOnly uint8
|
||||
TriggerPrice uint32
|
||||
OrderExpiry int64
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2MintSharesTxInfo)(nil)
|
||||
|
||||
type L2MintSharesTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
PublicPoolIndex int64
|
||||
ShareAmount int64
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2MintSharesTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2MintShares
|
||||
}
|
||||
|
||||
func (txInfo *L2MintSharesTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2MintSharesTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2MintSharesTxInfo) Validate() error {
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// PublicPoolIndex
|
||||
if txInfo.PublicPoolIndex < MinSubAccountIndex {
|
||||
return ErrPublicPoolIndexTooLow
|
||||
}
|
||||
if txInfo.PublicPoolIndex > MaxAccountIndex {
|
||||
return ErrPublicPoolIndexTooHigh
|
||||
}
|
||||
|
||||
if txInfo.ShareAmount < MinPoolSharesToMintOrBurn {
|
||||
return ErrPoolMintShareAmountTooLow
|
||||
}
|
||||
if txInfo.ShareAmount > MaxPoolSharesToMintOrBurn {
|
||||
return ErrPoolMintShareAmountTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2MintSharesTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 8)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2MintShares))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.PublicPoolIndex))
|
||||
elems = append(elems, g.FromInt64(txInfo.ShareAmount))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2ModifyOrderTxInfo)(nil)
|
||||
|
||||
type L2ModifyOrderTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
MarketIndex int16
|
||||
Index int64 // Client Order Index or Order Index of the order to modify
|
||||
BaseAmount int64
|
||||
Price uint32
|
||||
TriggerPrice uint32
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2ModifyOrderTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2ModifyOrder
|
||||
}
|
||||
|
||||
func (txInfo *L2ModifyOrderTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2ModifyOrderTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2ModifyOrderTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrAccountIndexTooHigh
|
||||
}
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// MarketIndex
|
||||
isSpotMarket := txInfo.MarketIndex >= MinSpotMarketIndex && txInfo.MarketIndex <= MaxSpotMarketIndex
|
||||
isPerpsMarket := txInfo.MarketIndex >= MinPerpsMarketIndex && txInfo.MarketIndex <= MaxPerpsMarketIndex
|
||||
if !isSpotMarket && !isPerpsMarket {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
// Index
|
||||
if txInfo.Index < MinClientOrderIndex && txInfo.Index < MinOrderIndex {
|
||||
return ErrClientOrderIndexTooLow
|
||||
}
|
||||
if txInfo.Index > MaxClientOrderIndex && txInfo.Index > MaxOrderIndex {
|
||||
return ErrClientOrderIndexTooHigh
|
||||
}
|
||||
|
||||
// BaseAmount
|
||||
if txInfo.BaseAmount != NilOrderBaseAmount && txInfo.BaseAmount < MinOrderBaseAmount {
|
||||
return ErrBaseAmountTooLow
|
||||
}
|
||||
if txInfo.BaseAmount > MaxOrderBaseAmount {
|
||||
return ErrBaseAmountTooHigh
|
||||
}
|
||||
|
||||
// Price
|
||||
if txInfo.Price < MinOrderPrice {
|
||||
return ErrPriceTooLow
|
||||
}
|
||||
if txInfo.Price > MaxOrderPrice {
|
||||
return ErrPriceTooHigh
|
||||
}
|
||||
|
||||
// TriggerPrice
|
||||
if (txInfo.TriggerPrice < MinOrderTriggerPrice || txInfo.TriggerPrice > MaxOrderTriggerPrice) && txInfo.TriggerPrice != NilOrderTriggerPrice {
|
||||
return ErrOrderTriggerPriceInvalid
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2ModifyOrderTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 11)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2ModifyOrder))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MarketIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.Index))
|
||||
elems = append(elems, g.FromInt64(txInfo.BaseAmount))
|
||||
elems = append(elems, g.FromUint32(txInfo.Price))
|
||||
elems = append(elems, g.FromUint32(txInfo.TriggerPrice))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2TransferTxInfo)(nil)
|
||||
|
||||
type L2TransferTxInfo struct {
|
||||
FromAccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
ToAccountIndex int64
|
||||
AssetIndex int16
|
||||
FromRouteType uint8
|
||||
ToRouteType uint8
|
||||
Amount int64
|
||||
USDCFee int64
|
||||
|
||||
Memo [32]byte
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
L1Sig string
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) Validate() error {
|
||||
// FromAccountIndex
|
||||
if txInfo.FromAccountIndex < MinAccountIndex+1 {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.FromAccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// ToAccountIndex
|
||||
if txInfo.ToAccountIndex < MinAccountIndex {
|
||||
return ErrToAccountIndexTooLow
|
||||
}
|
||||
if txInfo.ToAccountIndex > MaxAccountIndex {
|
||||
return ErrToAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// AssetIndex
|
||||
if txInfo.AssetIndex < MinAssetIndex {
|
||||
return ErrAssetIndexTooLow
|
||||
}
|
||||
if txInfo.AssetIndex > MaxAssetIndex {
|
||||
return ErrAssetIndexTooHigh
|
||||
}
|
||||
|
||||
// FromRouteType
|
||||
if txInfo.FromRouteType != AssetRouteType_Perps && txInfo.FromRouteType != AssetRouteType_Spot {
|
||||
return ErrRouteTypeInvalid
|
||||
}
|
||||
|
||||
// ToRouteType
|
||||
if txInfo.ToRouteType != AssetRouteType_Perps && txInfo.ToRouteType != AssetRouteType_Spot {
|
||||
return ErrRouteTypeInvalid
|
||||
}
|
||||
|
||||
if txInfo.Amount <= 0 {
|
||||
return ErrTransferAmountTooLow
|
||||
}
|
||||
if txInfo.Amount > MaxTransferAmount {
|
||||
return ErrTransferAmountTooHigh
|
||||
}
|
||||
|
||||
if txInfo.USDCFee < 0 {
|
||||
return ErrTransferFeeNegative
|
||||
}
|
||||
if txInfo.USDCFee > MaxTransferAmount {
|
||||
return ErrTransferFeeTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2Transfer
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) GetL1SignatureBody(chainId uint32) string {
|
||||
hexMemo := hex.EncodeToString(txInfo.Memo[:])
|
||||
hexMemo = strings.Replace(hexMemo, "0x", "", 1)
|
||||
|
||||
signatureBody := fmt.Sprintf(
|
||||
TemplateTransfer,
|
||||
getHex10FromUint64(uint64(txInfo.Nonce)),
|
||||
getHex10FromUint64(uint64(txInfo.FromAccountIndex)),
|
||||
getHex10FromUint64(uint64(txInfo.FromRouteType)),
|
||||
getHex10FromUint64(uint64(txInfo.ApiKeyIndex)),
|
||||
getHex10FromUint64(uint64(txInfo.ToAccountIndex)),
|
||||
getHex10FromUint64(uint64(txInfo.ToRouteType)),
|
||||
getHex10FromUint64(uint64(txInfo.AssetIndex)),
|
||||
getHex10FromUint64(uint64(txInfo.Amount)), //nolint:gosec
|
||||
getHex10FromUint64(uint64(txInfo.USDCFee)), //nolint:gosec
|
||||
getHex10FromUint64(uint64(chainId)), //nolint:gosec
|
||||
hexMemo,
|
||||
)
|
||||
return signatureBody
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) GetL1AddressBySignature(chainId uint32) common.Address {
|
||||
return calculateL1AddressBySignature(txInfo.GetL1SignatureBody(chainId), txInfo.L1Sig)
|
||||
}
|
||||
|
||||
func (txInfo *L2TransferTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 14)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2Transfer))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.FromAccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.ToAccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.AssetIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.FromRouteType)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ToRouteType)))
|
||||
elems = append(elems, g.FromUint64((uint64(txInfo.Amount))&0xFFFFFFFF)) //nolint:gosec
|
||||
elems = append(elems, g.FromUint64(uint64(txInfo.Amount)>>32)) //nolint:gosec
|
||||
elems = append(elems, g.FromUint64((uint64(txInfo.USDCFee))&0xFFFFFFFF)) //nolint:gosec
|
||||
elems = append(elems, g.FromUint64((uint64(txInfo.USDCFee))>>32)) //nolint:gosec
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2UpdateLeverageTxInfo)(nil)
|
||||
|
||||
type L2UpdateLeverageTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
MarketIndex int16
|
||||
InitialMarginFraction uint16
|
||||
MarginMode uint8
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateLeverageTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2UpdateLeverage
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateLeverageTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateLeverageTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateLeverageTxInfo) Validate() error {
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
// MarketIndex
|
||||
if txInfo.MarketIndex == NilMarketIndex {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
if txInfo.MarginMode != CrossMargin && txInfo.MarginMode != IsolatedMargin {
|
||||
return ErrInvalidMarginMode
|
||||
}
|
||||
|
||||
// InitialMarginFraction
|
||||
if txInfo.InitialMarginFraction <= 0 {
|
||||
return ErrInitialMarginFractionTooLow
|
||||
}
|
||||
if txInfo.InitialMarginFraction > uint16(MarginFractionTick) { //nolint:gosec
|
||||
return ErrInitialMarginFractionTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateLeverageTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 9)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2UpdateLeverage))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(int64(txInfo.MarketIndex)))
|
||||
elems = append(elems, g.FromInt64(int64(txInfo.InitialMarginFraction)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MarginMode)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2UpdateMarginTxInfo)(nil)
|
||||
|
||||
type L2UpdateMarginTxInfo struct {
|
||||
AccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
|
||||
MarketIndex int16
|
||||
USDCAmount int64
|
||||
Direction uint8
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateMarginTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2UpdateMargin
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateMarginTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateMarginTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateMarginTxInfo) Validate() error {
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// MarketIndex
|
||||
if txInfo.MarketIndex < MinPerpsMarketIndex || txInfo.MarketIndex > MaxPerpsMarketIndex {
|
||||
return ErrInvalidMarketIndex
|
||||
}
|
||||
|
||||
if txInfo.USDCAmount == 0 {
|
||||
return ErrTransferAmountTooLow
|
||||
}
|
||||
if txInfo.USDCAmount > MaxTransferAmount {
|
||||
return ErrTransferAmountTooHigh
|
||||
}
|
||||
if txInfo.Direction != RemoveFromIsolatedMargin && txInfo.Direction != AddToIsolatedMargin {
|
||||
return ErrInvalidUpdateMarginDirection
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdateMarginTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 10)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2UpdateMargin))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(int64(txInfo.MarketIndex)))
|
||||
elems = append(elems, g.FromUint64(uint64(txInfo.USDCAmount)&0xFFFFFFFF)) //nolint:gosec
|
||||
elems = append(elems, g.FromUint64(uint64(txInfo.USDCAmount)>>32)) //nolint:gosec
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.Direction)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2UpdatePublicPoolTxInfo)(nil)
|
||||
|
||||
type L2UpdatePublicPoolTxInfo struct {
|
||||
AccountIndex int64 // Master account index
|
||||
ApiKeyIndex uint8
|
||||
|
||||
PublicPoolIndex int64
|
||||
|
||||
Status uint8
|
||||
OperatorFee int64
|
||||
MinOperatorShareRate uint16
|
||||
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdatePublicPoolTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2UpdatePublicPool
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdatePublicPoolTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdatePublicPoolTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdatePublicPoolTxInfo) Validate() error {
|
||||
// AccountIndex
|
||||
if txInfo.AccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.AccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// PublicPoolIndex
|
||||
if txInfo.PublicPoolIndex < MinAccountIndex {
|
||||
return ErrPublicPoolIndexTooLow
|
||||
}
|
||||
if txInfo.PublicPoolIndex > MaxAccountIndex {
|
||||
return ErrPublicPoolIndexTooHigh
|
||||
}
|
||||
|
||||
// Status
|
||||
if txInfo.Status != 0 && txInfo.Status != 1 {
|
||||
return ErrInvalidPoolStatus
|
||||
}
|
||||
|
||||
// OperatorFee
|
||||
if txInfo.OperatorFee < 0 || txInfo.OperatorFee > FeeTick {
|
||||
return ErrInvalidPoolOperatorFee
|
||||
}
|
||||
|
||||
// MinOperatorShareRate
|
||||
if txInfo.MinOperatorShareRate > ShareTick {
|
||||
return ErrPoolMinOperatorShareRateTooHigh
|
||||
}
|
||||
|
||||
// Nonce
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2UpdatePublicPoolTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 10)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2UpdatePublicPool))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.AccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromInt64(txInfo.PublicPoolIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.Status)))
|
||||
elems = append(elems, g.FromInt64(txInfo.OperatorFee))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.MinOperatorShareRate)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
|
||||
gQuint "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
TemplateChangePubKey = "Register Lighter Account\n\npubkey: 0x%s\nnonce: %s\naccount index: %s\napi key index: %s\nOnly sign this message for a trusted client!"
|
||||
TemplateTransfer = "Transfer\n\nnonce: %s\nfrom: %s (route %s)\napi key: %s\nto: %s (route %s)\nasset: %s\namount: %s\nfee: %s" +
|
||||
"\nchainId: %s\nmemo: %s\nOnly sign this message for a trusted client!"
|
||||
TemplateSubAccount = "Create Lighter Sub Account\n\nmaster account index: %s\nOnly sign this message for a trusted client!"
|
||||
)
|
||||
|
||||
const SignatureLength = 80
|
||||
const L1SignatureLength = 65
|
||||
const PubKeyLength = gFp5.Bytes
|
||||
const HashLength = gQuint.Bytes
|
||||
|
||||
func IsValidPubKeyLength(bytes []byte) bool {
|
||||
return len(bytes) == gFp5.Bytes
|
||||
}
|
||||
|
||||
func IsZeroByteSlice(bytes []byte) bool {
|
||||
for _, s := range bytes {
|
||||
if s != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getTxInfo(tx interface{}) (string, error) {
|
||||
txInfoBytes, err := json.Marshal(tx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(txInfoBytes), nil
|
||||
}
|
||||
|
||||
func getHex10FromUint64(value uint64) string {
|
||||
v := hexutil.EncodeUint64(value)
|
||||
v = strings.Replace(v, "0x", "", 1)
|
||||
|
||||
// Make sure result has fixed bytes
|
||||
vBytes := []byte(v)
|
||||
if len(vBytes) < 16 {
|
||||
toAppend := make([]byte, 16-len(vBytes))
|
||||
for i := range toAppend {
|
||||
toAppend[i] = 48
|
||||
}
|
||||
vBytes = append(toAppend, vBytes...)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("0x%s", string(vBytes))
|
||||
}
|
||||
|
||||
func calculateL1AddressBySignature(signatureBody, l1Signature string) common.Address {
|
||||
message := accounts.TextHash([]byte(signatureBody))
|
||||
// Decode from signature string to get the signature byte array
|
||||
signatureContent, err := hexutil.Decode(l1Signature)
|
||||
if err != nil {
|
||||
return [20]byte{}
|
||||
}
|
||||
|
||||
// Transform yellow paper V from 27/28 to 0/1
|
||||
if signatureContent[64] >= 27 {
|
||||
signatureContent[64] -= 27
|
||||
}
|
||||
|
||||
// Calculate the public key from the signature and source string
|
||||
signaturePublicKey, err := crypto.SigToPub(message, signatureContent)
|
||||
if err != nil {
|
||||
return [20]byte{}
|
||||
}
|
||||
|
||||
// Calculate the address from the public key
|
||||
publicAddress := crypto.PubkeyToAddress(*signaturePublicKey)
|
||||
return publicAddress
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package txtypes
|
||||
|
||||
import (
|
||||
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
|
||||
p2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
|
||||
)
|
||||
|
||||
var _ TxInfo = (*L2WithdrawTxInfo)(nil)
|
||||
|
||||
type L2WithdrawTxInfo struct {
|
||||
FromAccountIndex int64
|
||||
ApiKeyIndex uint8
|
||||
AssetIndex int16
|
||||
RouteType uint8
|
||||
Amount uint64
|
||||
ExpiredAt int64
|
||||
Nonce int64
|
||||
Sig []byte
|
||||
SignedHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (txInfo *L2WithdrawTxInfo) Validate() error {
|
||||
if txInfo.FromAccountIndex < MinAccountIndex {
|
||||
return ErrFromAccountIndexTooLow
|
||||
}
|
||||
if txInfo.FromAccountIndex > MaxAccountIndex {
|
||||
return ErrFromAccountIndexTooHigh
|
||||
}
|
||||
|
||||
// ApiKeyIndex
|
||||
if txInfo.ApiKeyIndex < MinApiKeyIndex {
|
||||
return ErrApiKeyIndexTooLow
|
||||
}
|
||||
if txInfo.ApiKeyIndex > MaxApiKeyIndex {
|
||||
return ErrApiKeyIndexTooHigh
|
||||
}
|
||||
|
||||
// AssetIndex
|
||||
if txInfo.AssetIndex < MinAssetIndex {
|
||||
return ErrAssetIndexTooLow
|
||||
}
|
||||
if txInfo.AssetIndex > MaxAssetIndex {
|
||||
return ErrAssetIndexTooHigh
|
||||
}
|
||||
|
||||
// RouteType
|
||||
if txInfo.RouteType != AssetRouteType_Perps && txInfo.RouteType != AssetRouteType_Spot {
|
||||
return ErrRouteTypeInvalid
|
||||
}
|
||||
|
||||
// Amount
|
||||
if txInfo.Amount == 0 {
|
||||
return ErrWithdrawalAmountTooLow
|
||||
}
|
||||
if txInfo.Amount > MaxWithdrawalAmount {
|
||||
return ErrWithdrawalAmountTooHigh
|
||||
}
|
||||
|
||||
if txInfo.Nonce < MinNonce {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
|
||||
if txInfo.ExpiredAt < 0 || txInfo.ExpiredAt > MaxTimestamp {
|
||||
return ErrExpiredAtInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txInfo *L2WithdrawTxInfo) GetTxType() uint8 {
|
||||
return TxTypeL2Withdraw
|
||||
}
|
||||
|
||||
func (txInfo *L2WithdrawTxInfo) GetTxInfo() (string, error) {
|
||||
return getTxInfo(txInfo)
|
||||
}
|
||||
|
||||
func (txInfo *L2WithdrawTxInfo) GetTxHash() string {
|
||||
return txInfo.SignedHash
|
||||
}
|
||||
|
||||
func (txInfo *L2WithdrawTxInfo) Hash(lighterChainId uint32, extra ...g.Element) (msgHash []byte, err error) {
|
||||
elems := make([]g.Element, 0, 14)
|
||||
|
||||
elems = append(elems, g.FromUint32(lighterChainId))
|
||||
elems = append(elems, g.FromUint32(TxTypeL2Withdraw))
|
||||
elems = append(elems, g.FromInt64(txInfo.Nonce))
|
||||
elems = append(elems, g.FromInt64(txInfo.ExpiredAt))
|
||||
|
||||
elems = append(elems, g.FromInt64(txInfo.FromAccountIndex))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.ApiKeyIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.AssetIndex)))
|
||||
elems = append(elems, g.FromUint32(uint32(txInfo.RouteType)))
|
||||
elems = append(elems, g.FromUint64(uint64(txInfo.Amount&0xFFFFFFFF)))
|
||||
elems = append(elems, g.FromUint64(uint64(txInfo.Amount>>32)))
|
||||
|
||||
return p2.HashToQuinticExtension(elems).ToLittleEndianBytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/elliottech/lighter-go/client"
|
||||
"github.com/elliottech/lighter-go/client/http"
|
||||
"github.com/elliottech/lighter-go/types"
|
||||
"github.com/elliottech/lighter-go/types/txtypes"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
func wrapErr(err error) js.Value {
|
||||
if err != nil {
|
||||
return js.ValueOf(map[string]interface{}{"error": fmt.Sprintf("%v", err)})
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{})
|
||||
}
|
||||
|
||||
func messageToSign(info txtypes.TxInfo) string {
|
||||
switch tx := info.(type) {
|
||||
case *txtypes.L2ChangePubKeyTxInfo:
|
||||
return tx.GetL1SignatureBody()
|
||||
case *txtypes.L2TransferTxInfo:
|
||||
return tx.GetL1SignatureBody()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func convertTxInfoToJS(info txtypes.TxInfo, err error) js.Value {
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
if info == nil {
|
||||
return js.ValueOf(map[string]interface{}{"error": "nil response"})
|
||||
}
|
||||
|
||||
txInfoStr, strErr := info.GetTxInfo()
|
||||
if strErr != nil {
|
||||
return wrapErr(strErr)
|
||||
}
|
||||
|
||||
out := map[string]interface{}{
|
||||
"txType": info.GetTxType(),
|
||||
"txInfo": txInfoStr,
|
||||
"txHash": info.GetTxHash(),
|
||||
}
|
||||
if msg := messageToSign(info); msg != "" {
|
||||
out["messageToSign"] = msg
|
||||
}
|
||||
return js.ValueOf(out)
|
||||
}
|
||||
|
||||
// safeInt safely extracts an int from a js.Value, handling undefined values
|
||||
func safeInt(v js.Value, index int) (int64, error) {
|
||||
if v.Type() == js.TypeUndefined {
|
||||
return 0, fmt.Errorf("argument %d is undefined", index)
|
||||
}
|
||||
return int64(v.Int()), nil
|
||||
}
|
||||
|
||||
// safeUint8 safely extracts a uint8 from a js.Value, handling undefined values
|
||||
func safeUint8(v js.Value, index int) (uint8, error) {
|
||||
if v.Type() == js.TypeUndefined {
|
||||
return 0, fmt.Errorf("argument %d is undefined", index)
|
||||
}
|
||||
return uint8(v.Int()), nil
|
||||
}
|
||||
|
||||
// safeUint32 safely extracts a uint32 from a js.Value, handling undefined values
|
||||
func safeUint32(v js.Value, index int) (uint32, error) {
|
||||
if v.Type() == js.TypeUndefined {
|
||||
return 0, fmt.Errorf("argument %d is undefined", index)
|
||||
}
|
||||
return uint32(v.Int()), nil
|
||||
}
|
||||
|
||||
func getClient(args []js.Value) (*client.TxClient, error) {
|
||||
l := len(args)
|
||||
if l < 2 {
|
||||
return nil, fmt.Errorf("insufficient arguments: need at least 2 for apiKeyIndex and accountIndex")
|
||||
}
|
||||
// Check if the last two arguments are valid and extract safely
|
||||
if args[l-2].Type() == js.TypeUndefined || args[l-1].Type() == js.TypeUndefined {
|
||||
return nil, fmt.Errorf("apiKeyIndex or accountIndex is undefined")
|
||||
}
|
||||
apiKeyIndexVal, err := safeUint8(args[l-2], l-2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountIndexVal, err := safeInt(args[l-1], l-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.GetClient(apiKeyIndexVal, accountIndexVal)
|
||||
}
|
||||
|
||||
// recoverPanic wraps a function execution with panic recovery
|
||||
func recoverPanic(fn func() js.Value) (result js.Value) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
result = wrapErr(fmt.Errorf("panic: %v", r))
|
||||
}
|
||||
}()
|
||||
return fn()
|
||||
}
|
||||
|
||||
func main() {
|
||||
js.Global().Set("GenerateAPIKey", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 1 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "GenerateAPIKey expects 1 arg: seed"})
|
||||
}
|
||||
seed := args[0].String()
|
||||
privateKey, publicKey, err := client.GenerateAPIKey(seed)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{"privateKey": privateKey, "publicKey": publicKey})
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("CreateClient", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "CreateClient expects 5 args: url, privateKey, chainId, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
url := args[0].String()
|
||||
privateKey := args[1].String()
|
||||
chainId := uint32(args[2].Int())
|
||||
apiKeyIndex := uint8(args[3].Int())
|
||||
accountIndex := int64(args[4].Int())
|
||||
httpClient := http.NewClient(url)
|
||||
_, err := client.CreateClient(httpClient, privateKey, chainId, apiKeyIndex, accountIndex)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
return wrapErr(nil)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("CheckClient", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 2 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "CheckClient expects 2 args: apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
err = c.Check()
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
return wrapErr(nil)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("CreateAuthToken", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 3 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "CreateAuthToken expects 3 args: deadline, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
deadline := int64(args[0].Int())
|
||||
if deadline == 0 {
|
||||
deadline = time.Now().Add(time.Hour * 7).Unix()
|
||||
}
|
||||
|
||||
token, err := c.GetAuthToken(time.Unix(deadline, 0))
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{"authToken": token})
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignChangePubKey", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 4 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignChangePubKey expects 4 args: pubKeyHex, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
pubKeyHex := args[0].String()
|
||||
nonce := int64(args[1].Int())
|
||||
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
pubKeyBytes, err := hexutil.Decode(pubKeyHex)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
if len(pubKeyBytes) != 40 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "invalid pub key length. expected 40 but got " + strconv.Itoa(len(pubKeyBytes))})
|
||||
}
|
||||
var pubKey [40]byte
|
||||
copy(pubKey[:], pubKeyBytes)
|
||||
|
||||
txInfo := &types.ChangePubKeyReq{
|
||||
PubKey: pubKey,
|
||||
}
|
||||
ops := &types.TransactOpts{
|
||||
Nonce: &nonce,
|
||||
}
|
||||
|
||||
tx, err := c.GetChangePubKeyTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCreateOrder", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 13 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCreateOrder expects 13 args: marketIndex, clientOrderIndex, baseAmount, price, isAsk, orderType, timeInForce, reduceOnly, triggerPrice, orderExpiry, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
// Validate all arguments are defined before accessing
|
||||
for i := 0; i < 13; i++ {
|
||||
if args[i].Type() == js.TypeUndefined {
|
||||
return js.ValueOf(map[string]interface{}{"error": fmt.Sprintf("argument %d is undefined", i)})
|
||||
}
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
marketIndex, err := safeUint8(args[0], 0)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
clientOrderIndex, err := safeInt(args[1], 1)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
baseAmount, err := safeInt(args[2], 2)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
price, err := safeUint32(args[3], 3)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
isAsk, err := safeUint8(args[4], 4)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
orderType, err := safeUint8(args[5], 5)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
timeInForce, err := safeUint8(args[6], 6)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
reduceOnly, err := safeUint8(args[7], 7)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
triggerPrice, err := safeUint32(args[8], 8)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
orderExpiry, err := safeInt(args[9], 9)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
nonce, err := safeInt(args[10], 10)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
if orderExpiry == -1 {
|
||||
orderExpiry = time.Now().Add(time.Hour * 24 * 28).UnixMilli() // 28 days
|
||||
}
|
||||
|
||||
txInfo := &types.CreateOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
ClientOrderIndex: clientOrderIndex,
|
||||
BaseAmount: baseAmount,
|
||||
Price: price,
|
||||
IsAsk: isAsk,
|
||||
Type: orderType,
|
||||
TimeInForce: timeInForce,
|
||||
ReduceOnly: reduceOnly,
|
||||
TriggerPrice: triggerPrice,
|
||||
OrderExpiry: orderExpiry,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetCreateOrderTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCancelOrder", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCancelOrder expects 5 args: marketIndex, orderIndex, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
marketIndex := uint8(args[0].Int())
|
||||
orderIndex := int64(args[1].Int())
|
||||
nonce := int64(args[2].Int())
|
||||
|
||||
txInfo := &types.CancelOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
Index: orderIndex,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetCancelOrderTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCancelAllOrders", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCancelAllOrders expects 5 args: timeInForce, time, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
timeInForce := uint8(args[0].Int())
|
||||
timeVal := int64(args[1].Int())
|
||||
nonce := int64(args[2].Int())
|
||||
|
||||
txInfo := &types.CancelAllOrdersTxReq{
|
||||
TimeInForce: timeInForce,
|
||||
Time: timeVal,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetCancelAllOrdersTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignTransfer", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 7 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignTransfer expects 7 args: toAccount, usdcAmount, fee, memo, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
toAccount := int64(args[0].Int())
|
||||
usdcAmount := int64(args[1].Int())
|
||||
fee := int64(args[2].Int())
|
||||
memoStr := args[3].String()
|
||||
nonce := int64(args[4].Int())
|
||||
|
||||
var memoArr [32]byte
|
||||
bs := []byte(memoStr)
|
||||
if len(bs) != 32 {
|
||||
return wrapErr(fmt.Errorf("memo expected to be 32 bytes long"))
|
||||
}
|
||||
for i := 0; i < 32; i++ {
|
||||
memoArr[i] = bs[i]
|
||||
}
|
||||
|
||||
txInfo := &types.TransferTxReq{
|
||||
ToAccountIndex: toAccount,
|
||||
USDCAmount: usdcAmount,
|
||||
Fee: fee,
|
||||
Memo: memoArr,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetTransferTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignWithdraw", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 4 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignWithdraw expects 4 args: usdcAmount, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
usdcAmount := uint64(args[0].Int())
|
||||
nonce := int64(args[1].Int())
|
||||
|
||||
txInfo := &types.WithdrawTxReq{
|
||||
USDCAmount: usdcAmount,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetWithdrawTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignUpdateLeverage", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 6 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignUpdateLeverage expects 6 args: marketIndex, fraction, marginMode, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
marketIndex := uint8(args[0].Int())
|
||||
fraction := uint16(args[1].Int())
|
||||
marginMode := uint8(args[2].Int())
|
||||
nonce := int64(args[3].Int())
|
||||
|
||||
txInfo := &types.UpdateLeverageTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
InitialMarginFraction: fraction,
|
||||
MarginMode: marginMode,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetUpdateLeverageTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignModifyOrder", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 8 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignModifyOrder expects 8 args: marketIndex, index, baseAmount, price, triggerPrice, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
marketIndex := uint8(args[0].Int())
|
||||
index := int64(args[1].Int())
|
||||
baseAmount := int64(args[2].Int())
|
||||
price := uint32(args[3].Int())
|
||||
triggerPrice := uint32(args[4].Int())
|
||||
nonce := int64(args[5].Int())
|
||||
|
||||
txInfo := &types.ModifyOrderTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
Index: index,
|
||||
BaseAmount: baseAmount,
|
||||
Price: price,
|
||||
TriggerPrice: triggerPrice,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetModifyOrderTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCreateSubAccount", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 3 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCreateSubAccount expects 3 args: nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
nonce := int64(args[0].Int())
|
||||
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetCreateSubAccountTransaction(ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCreatePublicPool", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 6 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCreatePublicPool expects 6 args: operatorFee, initialTotalShares, minOperatorShareRate, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
operatorFee := int64(args[0].Int())
|
||||
initialTotalShares := int64(args[1].Int())
|
||||
minOperatorShareRate := int64(args[2].Int())
|
||||
nonce := int64(args[3].Int())
|
||||
|
||||
txInfo := &types.CreatePublicPoolTxReq{
|
||||
OperatorFee: operatorFee,
|
||||
InitialTotalShares: initialTotalShares,
|
||||
MinOperatorShareRate: minOperatorShareRate,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetCreatePublicPoolTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignUpdatePublicPool", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 7 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignUpdatePublicPool expects 7 args: publicPoolIndex, status, operatorFee, minOperatorShareRate, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := uint8(args[0].Int())
|
||||
status := uint8(args[1].Int())
|
||||
operatorFee := int64(args[2].Int())
|
||||
minOperatorShareRate := int64(args[3].Int())
|
||||
nonce := int64(args[4].Int())
|
||||
|
||||
txInfo := &types.UpdatePublicPoolTxReq{
|
||||
PublicPoolIndex: int64(publicPoolIndex),
|
||||
Status: status,
|
||||
OperatorFee: operatorFee,
|
||||
MinOperatorShareRate: minOperatorShareRate,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetUpdatePublicPoolTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignMintShares", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignMintShares expects 5 args: publicPoolIndex, shareAmount, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := int64(args[0].Int())
|
||||
shareAmount := int64(args[1].Int())
|
||||
nonce := int64(args[2].Int())
|
||||
|
||||
txInfo := &types.MintSharesTxReq{
|
||||
PublicPoolIndex: publicPoolIndex,
|
||||
ShareAmount: shareAmount,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetMintSharesTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignBurnShares", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignBurnShares expects 5 args: publicPoolIndex, shareAmount, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
publicPoolIndex := int64(args[0].Int())
|
||||
shareAmount := int64(args[1].Int())
|
||||
nonce := int64(args[2].Int())
|
||||
|
||||
txInfo := &types.BurnSharesTxReq{
|
||||
PublicPoolIndex: publicPoolIndex,
|
||||
ShareAmount: shareAmount,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetBurnSharesTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignUpdateMargin", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 6 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignUpdateMargin expects 6 args: marketIndex, usdcAmount, direction, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
marketIndex := uint8(args[0].Int())
|
||||
usdcAmount := int64(args[1].Int())
|
||||
direction := uint8(args[2].Int())
|
||||
nonce := int64(args[3].Int())
|
||||
|
||||
txInfo := &types.UpdateMarginTxReq{
|
||||
MarketIndex: marketIndex,
|
||||
USDCAmount: usdcAmount,
|
||||
Direction: direction,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
tx, err := c.GetUpdateMarginTransaction(txInfo, ops)
|
||||
return convertTxInfoToJS(tx, err)
|
||||
})
|
||||
}))
|
||||
|
||||
js.Global().Set("SignCreateGroupedOrders", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return recoverPanic(func() js.Value {
|
||||
if len(args) < 5 {
|
||||
return js.ValueOf(map[string]interface{}{"error": "SignCreateGroupedOrders expects 5 args: groupingType, orders array, nonce, apiKeyIndex, accountIndex"})
|
||||
}
|
||||
c, err := getClient(args)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
groupingType := uint8(args[0].Int())
|
||||
|
||||
// Parse orders array from JS
|
||||
ordersArg := args[1]
|
||||
if ordersArg.Type() != js.TypeObject {
|
||||
return js.ValueOf(map[string]interface{}{"error": "orders must be an array"})
|
||||
}
|
||||
length := ordersArg.Length()
|
||||
orders := make([]*types.CreateOrderTxReq, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
orderObj := ordersArg.Index(i)
|
||||
if orderObj.Type() != js.TypeObject {
|
||||
return js.ValueOf(map[string]interface{}{"error": fmt.Sprintf("order %d must be an object", i)})
|
||||
}
|
||||
|
||||
orderExpiry := int64(orderObj.Get("OrderExpiry").Int())
|
||||
if orderExpiry == -1 {
|
||||
orderExpiry = time.Now().Add(time.Hour * 24 * 28).UnixMilli()
|
||||
}
|
||||
|
||||
orders[i] = &types.CreateOrderTxReq{
|
||||
MarketIndex: uint8(orderObj.Get("MarketIndex").Int()),
|
||||
ClientOrderIndex: int64(orderObj.Get("ClientOrderIndex").Int()),
|
||||
BaseAmount: int64(orderObj.Get("BaseAmount").Int()),
|
||||
Price: uint32(orderObj.Get("Price").Int()),
|
||||
IsAsk: uint8(orderObj.Get("IsAsk").Int()),
|
||||
Type: uint8(orderObj.Get("Type").Int()),
|
||||
TimeInForce: uint8(orderObj.Get("TimeInForce").Int()),
|
||||
ReduceOnly: uint8(orderObj.Get("ReduceOnly").Int()),
|
||||
TriggerPrice: uint32(orderObj.Get("TriggerPrice").Int()),
|
||||
OrderExpiry: orderExpiry,
|
||||
}
|
||||
}
|
||||
|
||||
nonce := int64(args[2].Int())
|
||||
|
||||
req := &types.CreateGroupedOrdersTxReq{
|
||||
GroupingType: groupingType,
|
||||
Orders: orders,
|
||||
}
|
||||
ops := new(types.TransactOpts)
|
||||
if nonce != -1 {
|
||||
ops.Nonce = &nonce
|
||||
}
|
||||
|
||||
txInfo, err := c.GetCreateGroupedOrdersTransaction(req, ops)
|
||||
return convertTxInfoToJS(txInfo, err)
|
||||
})
|
||||
}))
|
||||
|
||||
select {}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) |
|
||||
@@ -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. **<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. **<mark style="color:red;">**`orders`**</mark>** 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. **<mark style="color:red;">**`subaccount_snapshots`**</mark>** 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 **<mark style="color:red;">**`summary`**</mark>** 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. **<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. **<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. **<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. **<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. **<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. **<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. **<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 <= <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 <= <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 < 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 <= <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> <= <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 <= <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> <= <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 < 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 <= <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> <= <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 <= <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> <= <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> <= <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> <= <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> <= <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 <= <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 < 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 <= <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> <= <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 <= <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 <= 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> <= <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 <= <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 <= <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 *****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 provided**:** 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 << 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\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* **<mark style="color:red;">**productId**</mark>** \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*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 user’s 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, 0–7)**</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, 9–10)**</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, 12–13)**</mark> – 0 = NONE, 1 = PRICE, 2 = TWAP, 3 = TWAP\_CUSTOM\_AMOUNTS
|
||||
* <mark style="color:red;">**Reserved (50 bits, 14–63)**</mark> – future use
|
||||
* <mark style="color:red;">**Value (64 bits, 64–127)**</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 you’d 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 aren’t <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 **<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.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Health Groups
|
||||
|
||||
{% hint style="info" %}
|
||||
**Note**: a health group is a perp and spot product whose health is calculated together (e.g. BTC and BTC-PERP).
|
||||
{% endhint %}
|
||||
|
||||
## 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": "health_groups"
|
||||
}
|
||||
```
|
||||
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="REST (GET)" %} <mark style="color:green;">**GET**</mark> `[GATEWAY_REST_ENDPOINT]/query?type=health_groups`
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="REST (POST)" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
|
||||
|
||||
**Message**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "health_groups"
|
||||
}
|
||||
```
|
||||
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
## Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"health_groups": [
|
||||
[
|
||||
1,
|
||||
2
|
||||
]
|
||||
]
|
||||
},
|
||||
"request_type": "query_health_groups"
|
||||
}
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
|
||||
* <mark style="color:red;">`health_groups`</mark>: list of all available health groups. **Note**: <mark style="color:red;">`health_groups[i]`</mark> is the spot / perp product pair of health group <mark style="color:red;">`i`</mark> where <mark style="color:red;">`health_groups[i][0]`</mark> is the spot <mark style="color:red;">`product_id`</mark> and <mark style="color:red;">`health_groups[i][1]`</mark> is the perp <mark style="color:red;">`product_id`</mark>. Additionally, it is possible for a health group to only have either a spot or perp product, in which case, the product that doesn’t exist is set to <mark style="color:red;">`0`</mark>.
|
||||
{% endhint %}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user