---
# System prepended metadata

title: Solana最佳入门项目：Jup限价单的简单实现

---

# 概述

本项目实现了一个基于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

