forked from tradeparadex/code-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathorder.groovy
More file actions
262 lines (233 loc) · 9.23 KB
/
order.groovy
File metadata and controls
262 lines (233 loc) · 9.23 KB
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/* groovylint-disable LineLength */
@Grapes([
@Grab('com.swmansion.starknet:starknet:0.7.3'),
@Grab('org.apache.commons:commons-math3:3.6.1'),
@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7'),
])
package com.paradex.api.rest
import com.swmansion.starknet.data.TypedData
import com.swmansion.starknet.data.types.Felt
import groovy.json.JsonBuilder
import groovyx.net.http.*
import groovyx.net.http.RESTClient
import java.lang.reflect.Method
import java.net.URL
import java.util.List
import java.util.stream.Collectors
import org.apache.commons.math3.util.BigReal
import paradex.StarknetCurve
class ParadexOrderExample {
static String apiUrl = "https://api.testnet.paradex.trade/v1"
static String chainId = '0x505249564154455f534e5f504f54435f5345504f4c4941' // `PRIVATE_SN_POTC_SEPOLIA`
// Enter values here
static String accountAddressStr = '' // PARADEX_ACCOUNT_ADDRESS
static String privateKeyStr = '' // PARADEX_PRIVATE_KEY
static void main(String... args) {
if (args.length == 1 && args[0] == "bench") {
println("Running benchmarks...")
benchmark()
return
}
def jwt = getJWT()
def size = new BigDecimal("0.1")
def orderPayload = buildOrder("MARKET", "BUY", size, "ETH-USD-PERP", "mock", chainId)
println "Order Payload: $orderPayload"
def requestHeaders = [
"Authorization": "Bearer ${jwt}"
]
postRestApi("${apiUrl}/orders", requestHeaders, orderPayload)
}
static String getJWT() {
// Get current timestamp in seconds
long timestamp = System.currentTimeMillis() / 1000
long expiry = timestamp + 24 * 60 * 60 // now + 24 hours
// Create the auth message
def authMessage = createAuthMessage(timestamp, expiry, chainId)
// Get signature
def (String signatureStr, String messageHashStr) = getSignature(authMessage)
// Call the auth endpoint
def requestHeaders = [
'PARADEX-STARKNET-ACCOUNT': accountAddressStr,
'PARADEX-STARKNET-SIGNATURE': signatureStr,
'PARADEX-STARKNET-MESSAGE-HASH': messageHashStr,
'PARADEX-TIMESTAMP': timestamp.toString(),
'PARADEX-SIGNATURE-EXPIRATION': expiry.toString(),
]
def jsonResponse = postRestApi("${apiUrl}/auth", requestHeaders, null)
def jwtToken = jsonResponse.jwt_token
return jwtToken
}
static getSignature(String message) {
// Convert the account address and private key to Felt types
Felt accountAddress = Felt.fromHex(accountAddressStr)
Felt privateKey = Felt.fromHex(privateKeyStr)
// Convert the message to a typed data object
TypedData typedData = TypedData.fromJsonString(message)
Felt messageHash = typedData.getMessageHash(accountAddress)
String messageHashStr = messageHash.hexString().toString()
// Sign message hash using paradex.StarknetCurve
List<Felt> signature = StarknetCurve.sign(privateKey, messageHash).toList()
// Convert the signature to a string
List<BigInteger> signatureBigInt = signature.collect { it.getValue().toBigInteger() }
def signatureStr = convertBigIntListToString(signatureBigInt)
return [signatureStr, messageHashStr]
}
static Object postRestApi(String url, Map headers, LinkedHashMap payload) {
println "Request: POST ${url}"
def http = new URL(url).openConnection() as HttpURLConnection
headers.each { key, value ->
http.setRequestProperty(key, value)
}
http.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
http.setRequestMethod('POST')
http.setDoOutput(true)
if (payload != null) {
def postBody = new groovy.json.JsonBuilder(payload).toPrettyString()
println "Request Body: ${postBody}"
http.outputStream.withWriter { writer ->
writer.write(postBody)
}
}
http.connect()
if (http.getResponseCode() >= 400) {
throw new Exception(http.getErrorStream().getText("UTF-8"))
} else {
// Response
def responseCode = http.responseCode
def responseData = http.inputStream.text
println "Response Status: ${responseCode}"
println "Response Data: ${responseData}"
def jsonResponse = new groovy.json.JsonSlurper().parseText(responseData)
return jsonResponse
}
}
static String convertBigIntListToString(List<BigInteger> list) {
def jsonArray = list.collect { "\"${it}\"" }.join(',')
return "[$jsonArray]"
}
static String createAuthMessage(long timestamp, long expiration, String chainId) {
return """
{
"message": {
"method": "POST",
"path": "/v1/auth",
"body": "",
"timestamp": %s,
"expiration": %s
},
"domain": {"name": "Paradex", "chainId": "%s", "version": "1"},
"primaryType": "Request",
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "chainId", "type": "felt"},
{"name": "version", "type": "felt"}
],
"Request": [
{"name": "method", "type": "felt"},
{"name": "path", "type": "felt"},
{"name": "body", "type": "felt"},
{"name": "timestamp", "type": "felt"},
{"name": "expiration", "type": "felt"}
]
}
}
""".formatted(timestamp, expiration, chainId)
}
static Map<String, Object> buildOrder(
String orderType,
String orderSide,
BigDecimal size,
String market,
String clientId,
String chainId
) {
long now = System.currentTimeMillis()
def order = [
client_id: clientId,
market: market,
side: orderSide,
signature_timestamp: now,
size: size.toString(),
type: orderType
]
def message = createOrderMessage(chainId, now, market, orderSide, orderType, size)
def (String signatureStr, String messageHashStr) = getSignature(message)
order.signature = signatureStr
return order
}
static String createOrderMessage(
String chainId,
long timestamp,
String market,
String side,
String orderType,
BigDecimal size,
BigDecimal price=0
) {
def chain_side=""
if (side == "BUY") {
chain_side="1"
} else {
chain_side="2"
}
def chain_price=""
if (orderType == 'MARKET') {
chain_price="0"
}
else {
chain_price=price.scaleByPowerOfTen(8).toBigInteger().toString()
}
def chain_size=size.scaleByPowerOfTen(8).toBigInteger().toString()
return """
{
"message": {
"timestamp": $timestamp,
"market": "$market",
"side": $chain_side,
"orderType": "$orderType",
"size": $chain_size,
"price": $chain_price
},
"domain": {"name": "Paradex", "chainId": "$chainId", "version": "1"},
"primaryType": "Order",
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "chainId", "type": "felt"},
{"name": "version", "type": "felt"}
],
"Order": [
{"name": "timestamp", "type": "felt"},
{"name": "market", "type": "felt"},
{"name": "side", "type": "felt"},
{"name": "orderType", "type": "felt"},
{"name": "size", "type": "felt"},
{"name": "price", "type": "felt"}
]
}
}
"""
}
// Benchmark
static benchmark() {
def runs = 2000
def startTime = System.currentTimeMillis()
(1..runs).each { _ ->
def clientId = "mock"
def market = "ETH-USD-PERP"
def orderSide = "BUY"
def orderType = "MARKET"
def size = new BigDecimal("0.1")
long now = System.currentTimeMillis()
def message = createOrderMessage(chainId, now, market, orderSide, orderType, size)
def (String signatureStr, String messageHashStr) = getSignature(message)
}
def endTime = System.currentTimeMillis()
def timeElapsed = (endTime - startTime)
def timeElapsedSec = timeElapsed / 1000
println("Total time for ${runs} orders: ${timeElapsedSec}s")
println("Average time per order: ${timeElapsed / runs}ms")
println("Result: ${runs / (timeElapsedSec)} signs/sec")
}
}