# 概述 本项目实现了一个基于Jupiter的限价单系统,用户可实时挂单、撤单,当代币价格达到挂单价格时进行交易。 # 技术 本项目需求较为简单,但是涵盖了以下知识点: 1. **jupiter api使用:** 怎么使用jupiter实现最佳的swap路由? 2. **jito api使用:** 怎么通过jito发送bundle或transaction以实现更快的上链速度? 3. **solana交易构造:** 怎么通过rust构造一笔solana的交易? 4. **rust的tokio、rocket框架的使用** # 执行流程 ## 挂单 用户给定以下数据进行挂单。 ```rust! #[derive(Deserialize)] pub struct PlaceOrderRequest { /// 输出代币 pub input_mint: String, /// 输出代币 pub output_mint: String, /// 卖出价格 pub price: f32, /// 数量 pub amount: u64, /// 滑点 pub slippage_bps: u16, /// 是否有小费给jito pub tip_amount: Option<u64>, /// 加密后的pk pub encrypt_pk: String, } ``` 注意:此处用户需要给定加密后的private_key,那么前后端之间的加密手段需要协调。如果无法托管私钥,也可以通过用户直接转账至中心化交易账户(用户撤单时转回即可)。 以下是一个http的挂单请求示例: ```jsonld! curl -X POST \ http://localhost:8000/place_order \ -H 'Content-Type: application/json' \ -d '{ "input_mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", "output_mint": "So11111111111111111111111111111111111111112", "price":0.738401, "amount": 1000, "slippage_bps": 50, "encrypt_pk": "xxx", "tip_amount":1000 }' ``` 此处,若指定了timp_amount则说明发交易时启用jito,如果不使用jito则把tip_amount去掉即可。 若执行成功,则返回一个order_id,可凭借order_id进行撤单。 ## 撤单 ```jsonld! curl -X POST \ http://localhost:8000/cancel_order \ -H 'Content-Type: application/json' \ -d '{ "order_id": "3e702c25-9c50-422d-a9dd-949df32b26c5" }' ``` # 内部实现 ## 核心逻辑设计 ![image](https://hackmd.io/_uploads/BkkX5M_skl.png) ## 如何成交? 为了实现最佳价格,使用Jupiter作为交易平台。Jupiter提供http请求进行交易构造,对于限价单场景来说,由于对性能的要求不高(可以挂限价单的代币其波动不应过大),因此使用Jupiter完全可满足。 Jupiter提供两种构造交易的方式: 1. 直接构造swap交易 2. 仅构造swap指令 此处为了实现抽佣的功能,选择构造swap指令的方式,而后将swap指令包含在交易内部。 ## 什么时候成交? 虽然说限价单是价格等于指定价格时执行买入卖出,由于价格不可能完全相等,因此还需要存在一定的误差阈值,此处有两种选择: 1. 价值误差:(当前价格*代币数量)与(挂单价格*代币数量)之间的绝对值小于阈值,则成交。 2. 价格误差:当前价格与挂单价格之间的绝对值小于阈值,则成交。 个人认为使用价值误差会更合适,因为此时还考虑了挂单的代币数量。试想如果价格差值为0.0001则成交,如果挂单大额代币,其损失数量也不小。 ## 如何抽佣? 抽佣即为用户每笔交易都需要抽取一定份额的佣金,对于swap过程中的抽佣,可分为以下情况: 1. **sol -> token**:在交易执行前进行佣金的抽取。 2. **token -> sol**:在交易执行后进行佣金的抽取。 3. **token -> token**:在交易时进行佣金的抽取。 如何理解呢? 在抽取佣金时,我们不希望佣金的形式是token,因为这种情况过于不稳定(代币价格波动),所以佣金必须是sol。 那么情况1和情况2就容易理解了,但是对于情况3,我的方式如下: 在用户的交易触发之间,抽取一部分佣金token,而后构造token -> sol的指令,再将这笔指令插入用户swap的指令当中。这么做的好处是:用户把所有交易费用都承担了😂。 ## 如何撤单? 这里利用`tokio select!`以及`oneshot channel`来完成。 当用户在挂单的过程中,构造一个oneshot channel(该通道设计用于一次性的通信),并且将该channel的sender存储下来(用户撤单时,使用sender往channel中写入消息)。而后,我们使用tokio select同时监听两个事件:`价格监听`和`oneshot channel是否收到消息`。当`oneshot channel receiver`收到消息时,说明需要撤单,此时将订单从订单簿中去除即可。 ## 如何对私钥进行加解密 加解密有两种方案: 1. 对称加密 2. 非对称加密 对称加密一般使用AES,非对称加密一般使用RSA。此处可自行调整。 # github: https://github.com/sasiyaluba/Jup-limit-order