跳到主要内容

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
}

参数说明

参数类型必填说明
symbolstring交易对(如 BTCUSDT)
sidestring方向:buysell
order_typestring订单类型:limitmarket
amountstring数量(字符串格式)
pricestring价格(限价单必填)
leverageinteger杠杆倍数(默认1)
signaturestringEIP-712 签名
timestampinteger时间戳(秒)

订单签名

# 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
)

签名流程

  1. 计算 Type Hash: 对类型字符串进行 keccak256
  2. 计算 Struct Hash: 编码所有参数并 keccak256
  3. 计算 Message Hash: keccak256(0x19 0x01 + domain_separator + struct_hash)
  4. 签名: 使用私钥对 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
}

最佳实践

1. 会话管理

class SessionManager:
def __init__(self, base_url, auth_manager):
self.session = requests.Session()
self.is_logged_in = False

def get_session(self):
if not self.is_logged_in:
self.login()
return self.session

2. 自动重试

def api_call_with_retry(func, max_retries=3):
for i in range(max_retries):
try:
return func()
except requests.exceptions.RequestException as e:
if i == max_retries - 1:
raise
time.sleep(2 ** i) # 指数退避

3. 签名缓存

# 对于相同参数的签名可以缓存
signature_cache = {}

def get_or_create_signature(params):
cache_key = hash(frozenset(params.items()))
if cache_key not in signature_cache:
signature_cache[cache_key] = sign(params)
return signature_cache[cache_key]

4. 批量操作

# 批量取消订单,每次最多20个
CHUNK_SIZE = 20

for i in range(0, len(order_ids), CHUNK_SIZE):
chunk = order_ids[i:i + CHUNK_SIZE]
cancel_orders(chunk)
time.sleep(0.1) # 限流保护

安全注意事项

⚠️ 重要提示

  1. 私钥安全:

    • 永远不要在代码中硬编码私钥
    • 使用环境变量或加密配置文件
    • 定期轮换私钥
  2. 签名验证:

    • 确保 Domain Separator 参数正确
    • 时间戳误差不超过 ±30秒
    • 验证签名格式(0x + 130字符)
  3. 请求安全:

    • 使用 HTTPS 连接
    • 验证服务器证书
    • 不要在日志中打印敏感信息
  4. 限流保护:

    • 遵守 API 限流规则
    • 实现指数退避重试
    • 批量操作添加延迟

相关文档