0%

构造NEAR Dapp 之 订单

前面我们走通了充提和签名,现在看订单相关:

  • 下单
  • 查看Orderbook
  • 取消订单
  • 查看订单列表

下单 place order

订单的基本数据:

1
2
3
4
5
6
7
const params = {
order_price: '2.4',
order_quantity: '1',
order_type: 'LIMIT',
side: 'SELL,
symbol: 'SPOT_NEAR_USDC',
}

接口地址:

1
const url = 'https://testnet-api.orderly.org/v1/order'

下单需要有两个签名:

  • orderlyKey 的签名
  • TradingKey的签名

tradingKey的签名逻辑是:

  1. 将订单参数按照key字典顺序排序
  2. 将排序的订单转换为key=value&key2=value2的字符串
  3. 将得到的字符串进行签名

首先,先创建签名函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const signMessageByTradingKey = (message: string, tradingKeyPair: any) => {
const ec = new EC('secp256k1');
const msgHash = keccak256(message);
const privateKey = tradingKeyPair.getPrivate('hex');
const signature = ec.sign(msgHash, privateKey, 'hex', { canonical: true });
const r = signature.r.toJSON();
const s = signature.s.toJSON();
return `${handleZero(r)}${handleZero(s)}0${signature.recoveryParam}`;
}

function handleZero(str: string) {
if (str.length < 64) {
const zeroArr = new Array(64 - str.length).fill(0);
return zeroArr.join('') + str;
}
return str;
}

在获得到签名后,放到params里面,然后让orderlyKey签名:

1
2
3
4
5
6
7
8
9
10
11
12
const tradingKeySignature = signMessageByTradingKey(orderMessage, tradingKey.keyPair);
params.signature = tradingKeySignature;

const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId)
const timestamp = new Date().getTime().toString();
const messageStr = [
timestamp,
'POST',
urlParam,
JSON.stringify(params),
].join('');
const orderlyKeySignature = signMessageByOrderlyKey(messageStr, keyPair);

PS:OrderlyKey签名的方法可以参照上一篇Near Dapp 充提

然后将签名信息和公钥放入到header里面:

1
2
3
4
5
6
7
Object.assign(headers, {
'orderly-account-id': accountId,
'orderly-key': keyPair?.getPublicKey().toString(),
'orderly-timestamp': timestamp,
'orderly-signature': orderlyKeySignature,
'orderly-trading-key': tradingKey?.publicKey.replace('04', ''),
});

总体就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
export const entryOrder = async (params: any) => {
const urlParam = '/v1/order';
const accountId = 'neardapp-t1.testnet';
const headers = {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json;charset=utf-8',
}

const orderMessage = Object.keys(params)
.sort()
.map((key: string) => `${key}=${params[key]}`)
.join('&',)

const tradingKey = getTradingKeyPair();
const tradingKeySignature = signMessageByTradingKey(orderMessage, tradingKey.keyPair);
console.log('sign', tradingKeySignature);
Object.assign(headers, {
'orderly-trading-key': tradingKey?.publicKey.replace('04', ''),
});
params.signature = tradingKeySignature;

const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId)
const timestamp = new Date().getTime().toString();
const messageStr = [
timestamp,
'POST',
urlParam,
JSON.stringify(params),
].join('');
const orderlyKeySignature = signMessageByOrderlyKey(messageStr, keyPair);

Object.assign(headers, {
'orderly-account-id': accountId,
'orderly-key': keyPair?.getPublicKey().toString(),
'orderly-timestamp': timestamp,
'orderly-signature': orderlyKeySignature,
});
return fetch(environment.config.apiUrl + urlParam, {
method: 'POST',
headers,
body: JSON.stringify(params),
})
.then(response => response.json());
}

然后调用这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
const placeOrder = () => {
const params: {[key: string]: any} = {
order_price: price, // '4'
order_quantity: amount, // 1
order_type: 'LIMIT',
side: type,
symbol: 'SPOT_NEAR_USDC',
}
entryOrder(params).then(res => {
console.log('place order res', res);
})
}

这样签名可以通过了,然后看接口返回的信息去看传递的参数是否有问题。

这里有个错误是下单的价格不在订单的有效区间。

那我们需要看下orderbook确定下当前区间在什么范围,然后调整价格。

查看Orderbook,我们可以直接去testnet看testnet-dex.woo.org

当然我们也可以通过api接口去查询。

接口地址:https://testnet-api.orderly.org/v1/orderbook/SPOT_NEAR_USDC

orderbook接口只需要orderlyKey的签名就可以了。

封装RequestUtil

鉴于我们在请求接口的时候,需要做orderlyKey和tradingKey的签名,所以我们来抽取一个独立的方法来管理。
request.util.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import {environment} from "../environment/environment";
import {getTradingKeyPair, signMessageByOrderlyKey, signMessageByTradingKey} from "../services/contract.service";

export enum MethodTypeEnum {
GET = 'GET',
POST = 'POST',
DELETE = 'DELETE',
}

async function get(url: string, params?: Object) {
const headers = await getOrderlySignature(url, MethodTypeEnum.GET);
return requestMethod(url, MethodTypeEnum.GET, params, headers);
}

async function post(url: string, params: { [key: string]: string }) {
const sign = getTradingKeySignature(params);
params['signature'] = sign.signature;
const headers = await getOrderlySignature(url, MethodTypeEnum.POST, params);
Object.assign(headers, {'orderly-trading-key': sign.tradingKey})
return requestMethod(url, MethodTypeEnum.POST, params, headers);
}

async function del(url: string, params: { [key: string]: string }) {
const sign = getTradingKeySignature(params);
params['signature'] = sign.signature;
url += `?${Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&')}`
const headers = await getOrderlySignature(url, MethodTypeEnum.DELETE);
Object.assign(headers, {
'Content-Type': 'application/x-www-form-urlencoded',
'orderly-trading-key': sign.tradingKey
})
return requestMethod(url, MethodTypeEnum.DELETE, null, headers);
}

const getOrderlySignature = async (url: string, method: MethodTypeEnum, params?: null | { [key: string]: string }): Promise<{ [key: string]: string }> => {
const accountId = 'neardapp-t1.testnet';
const urlParam = url.split(environment.config.apiUrl)[1];
const timestamp = new Date().getTime().toString();
let messageStr = [timestamp, method.toUpperCase(), urlParam].join('');
if (params && Object.keys(params).length) {
messageStr += JSON.stringify(params);
}
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId)
const sign = signMessageByOrderlyKey(messageStr, keyPair);

return {
'orderly-account-id': accountId,
'orderly-key': keyPair?.getPublicKey().toString(),
'orderly-timestamp': timestamp,
'orderly-signature': sign,
};
}

const getTradingKeySignature = (params: { [key: string]: string }) => {
const orderMessage = Object.keys(params)
.sort()
.map((key: string) => `${key}=${params[key]}`)
.join('&',)

const tradingKey = getTradingKeyPair();
const tradingKeySignature = signMessageByTradingKey(orderMessage, tradingKey.keyPair);
return {
tradingKey: tradingKey?.publicKey.replace('04', ''),
signature: tradingKeySignature,
}

}

const requestMethod = (url: string, method: MethodTypeEnum, params: any, headers: { [key: string]: string } = {}) => {
return fetch(url, {
method,
headers: Object.assign({
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json;charset=utf-8',
}, headers),
body: method === MethodTypeEnum.GET ? null : (params ? JSON.stringify(params) : null),

})
.then(response => response.json());
}

const requestUtil = {
get,
post,
del,
}
export default requestUtil;
}

const requestMethod = (url: string, method: MethodTypeEnum, params: any, headers: { [key: string]: string } = {}) => {
Object.assign(headers, {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json;charset=utf-8',
});

return fetch(url, {
method,
headers,
body: method === MethodTypeEnum.GET ? null : (params ? JSON.stringify(params) : null),

})
.then(response => response.json());
}

const requestUtil = {
get,
post,
}
export default requestUtil;

然后我们的下单和查询orderbook可以这样:

1
2
3
4
5
6
7
8
export const entryOrder = async (params: any) => {
return requestUtil.post(environment.config.apiUrl + '/v1/order', params);
}

export const orderbook = async(symbol: string = 'SPOT_NEAR_USDC') => {
const urlParam = '';
return requestUtil.get(`${environment.config.apiUrl}/v1/orderbook/${symbol}`)
}

这里定义delete方法是为了发送取消订单请求。

取消订单的方法类型是delete,参数是order_id和symbol,不同的点是签名的形式。

首先这个需要tradingKey的签名的,

正常获得tradingKey的签名后,将signature放入到params,然后将params拼接到url中,直接用url去做orderlyKey签名,params是不参与orderlyKey签名的。

来看下调用:

1
2
3
export const cancelOrder = async (params:{order_id: string; symbol: string}) => {
return requestUtil.del(environment.config.apiUrl + '/v1/order', params);
}

到这里我们的near-dapp基本成型了,有钱包的充提,可以查询Orderbook,以及可以下单(limit)、查看订单列表、取消订单等。

码字辛苦,打赏个咖啡☕️可好?💘