API 接口详解
机器人调用的核心 API 接口文档,包括认证、交易、账户管理等。
概述
做市机器人和刷量机器人都通过 Renance API 进行交易操作。所有API调用都需要经过 EIP-712 签名认证,确保安全性。
认证流程
1. 获取 Nonce
GET /api/v1/auth/nonce/{address}
请求示例:
curl "https://api.renance.xyz/api/v1/auth/nonce/0x1234...5678"
响应示例:
{
"nonce": 12345,
"typed_data": {
"types": {
"EIP712Domain": [...],
"Login": [...]
},
"domain": {
"name": "XBlade Vault",
"version": "1",
"chainId": 421614,
"verifyingContract": "0xFDe43f8e6e082975d246844DEF4fE8E704403d43"
},
"message": {
"wallet": "0x1234...5678",
"nonce": 12345,
"timestamp": 1704537600
}
}
}
2. 签名登录消息
使用 EIP-712 标准签名登录消息:
# EIP-712 Login Type Hash
LOGIN_TYPEHASH = "Login(address wallet,uint256 nonce,uint256 timestamp)"
# Domain Separator
domain_separator = keccak(
domain_type_hash +
keccak(b"XBlade Vault") +
keccak(b"1") +
encode(['uint256'], [chain_id]) +
encode(['address'], [verifying_contract])
)
# Struct Hash
struct_hash = keccak(encode(
['bytes32', 'address', 'uint256', 'uint256'],
[type_hash, wallet_address, nonce, timestamp]
))
# Message Hash
message_hash = keccak(b'\x19\x01' + domain_separator + struct_hash)
# Sign
signature = account.signHash(message_hash).signature.hex()
3. 登录
POST /api/v1/auth/login
请求体:
{
"address": "0x1234...5678",
"signature": "0xabcd...ef01",
"timestamp": 1704537600
}
响应示例:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"address": "0x1234...5678"
}
}
后续请求: 所有后续API请求都需要在请求头中包含 token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
交易接口
下单
POST /api/v1/orders
请求体:
{
"symbol": "BTCUSDT",
"side": "buy",
"order_type": "limit",
"amount": "0.001",
"price": "100000.00",
"leverage": 50,
"signature": "0xabcd...ef01",
"timestamp": 1704537600
}
参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
symbol | string | 是 | 交易对(如 BTCUSDT) |
side | string | 是 | 方向:buy 或 sell |
order_type | string | 是 | 订单类型:limit 或 market |
amount | string | 是 | 数量(字符串格式) |
price | string | 否 | 价格(限价单必填) |
leverage | integer | 否 | 杠杆倍数(默认1) |
signature | string | 是 | EIP-712 签名 |
timestamp | integer | 是 | 时间戳(秒) |
订单签名:
# EIP-712 CreateOrder Type Hash
CREATE_ORDER_TYPEHASH = "CreateOrder(address wallet,string symbol,string side,string orderType,string price,string amount,uint32 leverage,uint256 timestamp)"
# Struct Hash
struct_hash = keccak(encode(
['bytes32', 'address', 'bytes32', 'bytes32', 'bytes32', 'bytes32', 'bytes32', 'uint32', 'uint256'],
[
type_hash,
wallet_address,
keccak(symbol.encode()),
keccak(side.encode()),
keccak(order_type.encode()),
keccak(price.encode()),
keccak(amount.encode()),
leverage,
timestamp
]
))
# Message Hash
message_hash = keccak(b'\x19\x01' + domain_separator + struct_hash)
# Sign
signature = account.signHash(message_hash).signature.hex()
响应示例:
{
"id": "order_123456",
"symbol": "BTCUSDT",
"side": "buy",
"type": "limit",
"amount": "0.001",
"price": "100000.00",
"status": "open",
"created_at": "2026-01-06T14:00:00Z"
}
取消单个订单
POST /api/v1/orders/batch
请求体:
{
"order_ids": ["order_123456"],
"signature": "0xabcd...ef01",
"timestamp": 1704537600
}
取消订单签名:
# EIP-712 BatchCancelOrders Type Hash
BATCH_CANCEL_TYPEHASH = "BatchCancelOrders(address wallet,string orderIds,uint256 timestamp)"
# 拼接订单ID
order_ids_str = ','.join(order_ids)
# Struct Hash
struct_hash = keccak(encode(
['bytes32', 'address', 'bytes32', 'uint256'],
[type_hash, wallet_address, keccak(order_ids_str.encode()), timestamp]
))
# Message Hash 和签名同上
批量取消订单
同样使用 /api/v1/orders/batch 端点,但 order_ids 数组包含多个订单ID:
{
"order_ids": ["order_123456", "order_123457", "order_123458"],
"signature": "0xabcd...ef01",
"timestamp": 1704537600
}
账户接口
查询余额
GET /api/v1/account/balances
请求头:
Authorization: Bearer {token}
响应示例:
{
"balances": [
{
"asset": "USDT",
"token": "USDT",
"total": "50000.00",
"frozen": "15000.00",
"available": "35000.00"
}
]
}
查询持仓
GET /api/v1/account/positions
查询参数:
symbol(可选): 过滤特定交易对
响应示例:
{
"positions": [
{
"symbol": "BTCUSDT",
"side": "long",
"size": "0.5",
"entry_price": "100000.00",
"mark_price": "100500.00",
"unrealized_pnl": "250.00",
"leverage": 50
}
]
}
查询挂单
GET /api/v1/account/orders
查询参数:
status: 订单状态(如open)symbol(可选): 过滤特定交易对
响应示例:
{
"orders": [
{
"id": "order_123456",
"symbol": "BTCUSDT",
"side": "buy",
"type": "limit",
"amount": "0.001",
"price": "99950.00",
"filled": "0.000",
"status": "open",
"created_at": "2026-01-06T14:00:00Z"
}
]
}
充值提现接口
充值准备
POST /api/v1/deposit/prepare
请求体:
{
"amount": "10000.00",
"currency": "USDT",
"token": "USDT"
}
说明:
- 通知后端即将进行链上充值
- 实际充值需要调用链上 Vault 合约的
deposit()方法
提现请求
POST /api/v1/withdraw/request
请求体:
{
"token": "USDT",
"amount": "5000.00"
}
响应示例:
{
"withdraw_id": "withdraw_123456",
"backend_signature": "0xabcd...ef01",
"expiry": 1704541200,
"vault_address": "0xFDe43f8e6e082975d246844DEF4fE8E704403d43"
}
说明:
- 获取后端签名后,需要调用链上 Vault 合约的
withdraw()方法 - 提现完成后调用确认接口
提现确认
POST /api/v1/withdraw/{withdraw_id}/confirm
请求体:
{
"tx_hash": "0x1234...5678"
}
市场数据接口
K线数据
GET /api/v1/markets/{symbol}/candles
查询参数:
period: K线周期(如1m,5m,1h,1d)
响应示例:
[
{
"timestamp": 1704537600,
"open": "100000.00",
"high": "100500.00",
"low": "99800.00",
"close": "100200.00",
"volume": "1234.56"
}
]
代码示例
Python 完整示例
import requests
from eth_account import Account
from eth_utils import keccak
from eth_abi import encode
class RenanceClient:
def __init__(self, base_url, private_key):
self.base_url = base_url
self.account = Account.from_key(private_key)
self.address = self.account.address
self.session = requests.Session()
self.token = None
def login(self):
# 1. 获取 nonce
nonce_url = f"{self.base_url}/api/v1/auth/nonce/{self.address.lower()}"
resp = self.session.get(nonce_url)
data = resp.json()
nonce = data['nonce']
timestamp = data['typed_data']['message']['timestamp']
# 2. 签名
signature = self.sign_login(nonce, timestamp)
# 3. 登录
login_url = f"{self.base_url}/api/v1/auth/login"
resp = self.session.post(login_url, json={
"address": self.address.lower(),
"signature": signature,
"timestamp": timestamp
})
self.token = resp.json()['token']
self.session.headers.update({
'Authorization': f'Bearer {self.token}'
})
def place_order(self, symbol, side, order_type, amount, price=None, leverage=1):
import time
timestamp = int(time.time())
# 签名订单
signature = self.sign_order(
symbol, side, order_type, amount, price, leverage, timestamp
)
# 下单
url = f"{self.base_url}/api/v1/orders"
resp = self.session.post(url, json={
"symbol": symbol,
"side": side.lower(),
"order_type": order_type.lower(),
"amount": str(amount),
"price": str(price) if price else None,
"leverage": leverage,
"signature": signature,
"timestamp": timestamp
})
return resp.json()
def get_balances(self):
url = f"{self.base_url}/api/v1/account/balances"
resp = self.session.get(url)
return resp.json()
def cancel_all_orders(self, symbol=None):
# 获取挂单
orders = self.get_open_orders(symbol)
order_ids = [o['id'] for o in orders]
if not order_ids:
return
# 批量取消
import time
timestamp = int(time.time())
signature = self.sign_batch_cancel(order_ids, timestamp)
url = f"{self.base_url}/api/v1/orders/batch"
resp = self.session.post(url, json={
"order_ids": order_ids,
"signature": signature,
"timestamp": timestamp
})
return resp.json()
EIP-712 签名详解
Domain Separator
所有签名都使用相同的 Domain Separator:
domain_type_hash = keccak(
b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
)
domain_separator = keccak(
domain_type_hash +
keccak(b"XBlade Vault") + # name
keccak(b"1") + # version
encode(['uint256'], [421614]) + # chainId (Arbitrum Sepolia)
encode(['address'], ["0xFDe43f8e6e082975d246844DEF4fE8E704403d43"]) # verifyingContract
)
签名流程
- 计算 Type Hash: 对类型字符串进行 keccak256
- 计算 Struct Hash: 编码所有参数并 keccak256
- 计算 Message Hash:
keccak256(0x19 0x01 + domain_separator + struct_hash) - 签名: 使用私钥对 Message Hash 进行签名
类型定义
| 操作 | Type Hash |
|---|---|
| 登录 | Login(address wallet,uint256 nonce,uint256 timestamp) |
| 下单 | CreateOrder(address wallet,string symbol,string side,string orderType,string price,string amount,uint32 leverage,uint256 timestamp) |
| 取消单个订单 | CancelOrder(address wallet,string orderId,uint256 timestamp) |
| 批量取消 | BatchCancelOrders(address wallet,string orderIds,uint256 timestamp) |
错误处理
常见错误码
| 状态码 | 说明 | 解决方法 |
|---|---|---|
| 401 | 未授权 | 重新登录获取 token |
| 400 | 参数错误 | 检查请求参数格式 |
| 403 | 签名验证失败 | 检查签名逻辑和参数 |
| 429 | 请求过多 | 降低请求频率 |
| 500 | 服务器错误 | 稍后重试 |
错误响应示例
{
"error": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance for order",
"code": 400
}