Uniswap与PancakeSwap
Uniswap交易所对象在一条链上连接Uniswap或PancakeSwap的V2、V3资金池,把链上兑换映射为现货交易函数:用exchange.GetTicker()看价格、用exchange.CreateOrder()下单,不需要自己注册ABI、编码合约调用。选路、询价、代币授权、价格保护、发送交易都由交易所对象完成。
什么时候用Uniswap交易所对象,什么时候用Web3
- 在Uniswap、PancakeSwap上兑换代币:用Uniswap交易所对象。
- 调用其它合约、DEX的其它功能(如提供流动性、管理V3头寸)、其它链、自定义交易:用Web3交易所对象,见
以太坊(EVM)。
一个策略可以同时添加两种交易所对象,使用同一个钱包。
配置交易所对象
| 字段 | 说明 |
|---|---|
| DEX | Uniswap或PancakeSwap |
| Chain | Ethereum、Arbitrum、Base、BNB Chain。一个交易所对象只对应一条链上的一个DEX |
| Private Key | 钱包私钥(十六进制字符串)。支持把私钥本地化部署在托管者上,参看密钥安全性 |
| Rpc Address | 该链的节点地址,选择Chain时自动填入公共节点(如以太坊为https://ethereum-rpc.publicnode.com)。可以写多个节点,用逗号分隔,互为备用 |
| Rpc Api Key | 节点鉴权,可以留空。写成名称: 值时作为该名称的请求头发送,否则作为Authorization: Basic <值>发送 |
第一次调用时会核对节点所在的链与Chain是否一致,不一致时报错,不会把交易发到别的链上。钱包里需要有该链的原生币(ETH或BNB)支付gas。
交易对
- 交易对写作
ETH_USDC、UNI_USDT这样的基础币_计价币。 - 代币名按以下顺序解析:内置的常用代币(原生币、包装币、USDC、USDT等)→ 用
exchange.IO("token", 名字, 合约地址)登记的代币 → 官方代币列表。官方列表中同一条链上有同名代币时,请改用合约地址。 - 不在代币表中的代币可以直接用合约地址作为交易对的一部分,例如
0x1f9840a85d5af5bf1d1762f925bdaddc4201f984_USDC。 - 原生币与包装币是两种资产:
ETH与WETH、BNB与WBNB分别是不同的币。交易原生币时路由合约会自动包装、解包。两者之间的转换不能下单,用exchange.IO("wrap", 数量)、exchange.IO("unwrap", 数量)直接调用包装币合约,1:1兑换,只花gas。 exchange.GetMarkets()只列出常用的交易对,没有列出的交易对同样可以交易。
标准函数的含义
| 函数 | 行为 |
|---|---|
exchange.GetTicker() | 买一、卖一是按一定规模实际询价得到的可成交价格(已包含池子手续费);链上没有24小时统计 |
exchange.GetDepth() | 按逐档递增的规模在链上询价,推算出的价位,不是真实的挂单簿 |
exchange.GetTrades() | 该交易对资金池最近的链上兑换记录 |
exchange.GetAccount()、exchange.GetAssets() | 钱包中原生币和代币表中各代币的余额 |
exchange.CreateOrder() | 立即在链上兑换,见下文 |
exchange.GetOrder() | 订单ID就是交易哈希,状态来自交易回执:上链前为未完成,上链后为成交或失败 |
exchange.GetOrders() | 本次运行发出、还没有上链的订单 |
exchange.CancelOrder() | 用同一个nonce发送一笔替换交易,尽力撤销,见下文 |
不支持exchange.GetRecords()、exchange.GetTickers()、exchange.GetHistoryOrders()。
下单
DEX没有挂单簿,每笔订单都是一次立即执行的链上兑换:要么整笔成交,要么整笔回滚(只损失gas),不会部分成交、也不会挂在那里等价格。
- 限价单:限价是最差成交价。下单时先询价,按当前价格达不到限价时直接报错,不发交易;达得到时把「最少得到/最多支付」写进链上交易,交易上链前价格变动导致达不到时整笔回滚。
- 市价单:按询价结果扣除滑点得到最少得到/最多支付的数量,滑点默认0.5%,用
exchange.IO("slippage", 比例)修改。 - 数量:卖出时是卖出的基础币数量;限价买入时是要买到的基础币数量;市价买入时是要花费的计价币数量。
- 单笔订单可以在方向参数后附加设置,例如
exchange.CreateOrder("ETH_USDC", 'sell;{"slippage":0.01,"route":"v3"}', -1, 0.1):slippage为本单滑点,route限定路径类型(v2、v3、hop两跳、direct直连)。 - 卖出代币(ERC20)前会检查路由合约的授权额度,不够时先发送授权交易并等待上链。默认只授权本次需要的数量,
exchange.IO("approve", "max")改为无限授权,省去之后的授权交易。 - 交易超过截止时间(默认120秒,
exchange.IO("deadline", 秒数)修改)仍未上链时会回滚,避免在价格大幅变化后才成交。
撤单
exchange.CancelOrder()用原订单的nonce发送一笔转给自己的0金额交易,手续费更高,先上链则原订单失效。这只是尽力撤销:原订单可能在替换交易之前上链并成交;原订单已经上链时撤单直接报错。撤单后用exchange.GetOrder()确认最终状态。
常用的exchange.IO()指令
| 指令 | 作用 |
|---|---|
exchange.IO("slippage", 比例) | 市价单滑点,默认0.005 |
exchange.IO("deadline", 秒数) | 交易截止时间,默认120秒 |
exchange.IO("gasMultiplier", 倍数) | gas上限 = 节点估算值 × 倍数,默认1.2 |
exchange.IO("approve", "exact" 或 "max") | 授权模式 |
exchange.IO("token", 名字, 合约地址) | 登记代币;不传参数时列出代币表 |
exchange.IO("route", 交易对, 方向, 数量) | 只询价:各候选路径的报价和最优路径,不下单 |
exchange.IO("simulate", 交易对, 方向, 数量[, 价格]) | 按下单逻辑构造交易,只在链上模拟执行,不花gas |
exchange.IO("transfer", 收款地址, 数量[, 代币]) | 转出原生币或代币,数量可以写"all" |
exchange.IO("receipt", 交易哈希[, 等待毫秒]) | 查询转账等交易的回执,可以等待上链 |
exchange.IO("wrap", 数量)、exchange.IO("unwrap", 数量) | 原生币与包装币1:1互换 |
exchange.IO("contracts") | 当前DEX在当前链上的合约地址 |
exchange.IO("base", 节点地址)、exchange.IO("sendBase", 节点地址) | 切换节点;设置只用于广播交易的节点(私有交易通道) |
exchange.IO("address") | 钱包地址 |
各指令的参数与返回值见语法手册Uniswap分类。
示例
示例:询价、模拟,然后市价卖出
以以太坊上的ETH_USDC为例。注意CreateOrder会发出真实交易。
javascript
function main() {
var symbol = "ETH_USDC"
exchange.IO("slippage", 0.003) // 市价单滑点 0.3%
var t = exchange.GetTicker(symbol)
Log("买一:", t.Buy, "卖一:", t.Sell)
// 只询价:卖出 0.1 ETH 的最优路径
var r = exchange.IO("route", symbol, "sell", 0.1)
Log("最优路径:", r.best, "价格:", r.price)
// 链上模拟一遍,不花 gas
if (!exchange.IO("simulate", symbol, "sell", 0.1)) {
Log("模拟失败:", GetLastError())
return
}
// 市价卖出 0.1 ETH,订单 ID 是交易哈希
var id = exchange.CreateOrder(symbol, "sell", -1, 0.1)
if (!id) {
Log("下单失败:", GetLastError())
return
}
while (true) {
var o = exchange.GetOrder(id)
if (o && o.Status != ORDER_STATE_PENDING) {
Log("状态:", o.Status, "成交数量:", o.DealAmount, "成交均价:", o.AvgPrice)
break
}
Sleep(3000)
}
}参考