@optimex-xyz/market-maker-sdk 0.9.0-staging-adbbc25 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +662 -244
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# PMM API Integration Documentation
|
|
2
2
|
|
|
3
|
-
> **CHANGELOG (v0.8.0)**:
|
|
3
|
+
> **CHANGELOG (v0.8.0)**:
|
|
4
|
+
>
|
|
4
5
|
> - **Breaking Changes:**
|
|
5
6
|
> - Update to get router contract from protocol fetcher
|
|
6
7
|
> - Use consistent trade_id across all protocol
|
|
@@ -15,23 +16,85 @@ A comprehensive guide for implementing Private Market Makers (PMMs) in the cross
|
|
|
15
16
|
|
|
16
17
|
## Table of Contents
|
|
17
18
|
|
|
18
|
-
- [PMM API Integration Documentation](#pmm-api-integration-documentation)
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
19
|
+
- [PMM API Integration Documentation](#pmm-api-integration-documentation)
|
|
20
|
+
- [Table of Contents](#table-of-contents)
|
|
21
|
+
- [Smart Contract Integration](#smart-contract-integration)
|
|
22
|
+
- [Contract Addresses](#contract-addresses)
|
|
23
|
+
- [1. Overview](#1-overview)
|
|
24
|
+
- [1.1. Integration Flow](#11-integration-flow)
|
|
25
|
+
- [2. Quick Start](#2-quick-start)
|
|
26
|
+
- [2.1. API Environments](#21-api-environments)
|
|
21
27
|
- [3. PMM Backend APIs](#3-pmm-backend-apis)
|
|
22
|
-
- [3.1. Endpoint: `/indicative-quote`](#31-endpoint-indicative-quote)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
- [3.1. Endpoint: `/indicative-quote`](#31-endpoint-indicative-quote)
|
|
29
|
+
- [Description](#description)
|
|
30
|
+
- [Request Parameters](#request-parameters)
|
|
31
|
+
- [Example Request](#example-request)
|
|
32
|
+
- [Expected Response](#expected-response)
|
|
33
|
+
- [3.2. Endpoint: `/commitment-quote`](#32-endpoint-commitment-quote)
|
|
34
|
+
- [Description](#description-1)
|
|
35
|
+
- [Request Parameters](#request-parameters-1)
|
|
36
|
+
- [Example Request](#example-request-1)
|
|
37
|
+
- [Expected Response](#expected-response-1)
|
|
38
|
+
- [3.3. Endpoint: `/liquidation-quote`](#33-endpoint-liquidation-quote)
|
|
39
|
+
- [Description](#description-2)
|
|
40
|
+
- [Request Parameters](#request-parameters-2)
|
|
41
|
+
- [Example Request](#example-request-2)
|
|
42
|
+
- [Expected Response](#expected-response-2)
|
|
43
|
+
- [3.4. Endpoint: `/settlement-signature`](#34-endpoint-settlement-signature)
|
|
44
|
+
- [Description](#description-3)
|
|
45
|
+
- [Request Parameters](#request-parameters-3)
|
|
46
|
+
- [Example Request](#example-request-3)
|
|
47
|
+
- [Expected Response](#expected-response-3)
|
|
48
|
+
- [3.5. Endpoint: `/ack-settlement`](#35-endpoint-ack-settlement)
|
|
49
|
+
- [Description](#description-4)
|
|
50
|
+
- [Request Parameters](#request-parameters-4)
|
|
51
|
+
- [Example Request](#example-request-4)
|
|
52
|
+
- [Expected Response](#expected-response-4)
|
|
53
|
+
- [3.6. Endpoint: `/signal-payment`](#36-endpoint-signal-payment)
|
|
54
|
+
- [Description](#description-5)
|
|
55
|
+
- [Request Parameters](#request-parameters-5)
|
|
56
|
+
- [Example Request](#example-request-5)
|
|
57
|
+
- [Expected Response](#expected-response-5)
|
|
27
58
|
- [4. Solver API Endpoints for PMMs](#4-solver-api-endpoints-for-pmms)
|
|
28
59
|
- [4.1. Endpoint: `/v1/market-maker/tokens`](#41-endpoint-v1market-makertokens)
|
|
60
|
+
- [Description](#description-6)
|
|
61
|
+
- [Request Parameters](#request-parameters-6)
|
|
62
|
+
- [Example Request](#example-request-6)
|
|
63
|
+
- [Expected Response](#expected-response-6)
|
|
29
64
|
- [4.2. Endpoint: `/v1/market-maker/submit-settlement-tx`](#42-endpoint-v1market-makersubmit-settlement-tx)
|
|
65
|
+
- [Description](#description-7)
|
|
66
|
+
- [Request Parameters](#request-parameters-7)
|
|
67
|
+
- [Example Request](#example-request-7)
|
|
68
|
+
- [Expected Response](#expected-response-7)
|
|
69
|
+
- [Notes](#notes)
|
|
30
70
|
- [4.3. Endpoint: `/v1/market-maker/trades/:tradeId`](#43-endpoint-v1market-makertradestradeid)
|
|
71
|
+
- [Description](#description-8)
|
|
72
|
+
- [Request Parameters](#request-parameters-8)
|
|
73
|
+
- [Example Request](#example-request-8)
|
|
74
|
+
- [Expected Response](#expected-response-8)
|
|
31
75
|
- [5. PMM Making Payment](#5-pmm-making-payment)
|
|
32
76
|
- [5.1. EVM](#51-evm)
|
|
33
77
|
- [5.2. Bitcoin](#52-bitcoin)
|
|
34
78
|
|
|
79
|
+
## Smart Contract Integration
|
|
80
|
+
|
|
81
|
+
### Contract Addresses
|
|
82
|
+
|
|
83
|
+
**Testnet**
|
|
84
|
+
|
|
85
|
+
| Contract | Address |
|
|
86
|
+
| -------- | -------------------------------------------- |
|
|
87
|
+
| Signer | `0xA89F5060B810F3b6027D7663880c43ee77A865C7` |
|
|
88
|
+
| Router | `0x31C88ebd9E430455487b6a5c8971e8eF63e97ED4` |
|
|
89
|
+
| Payment | `0x7387DcCfE2f1D5F80b4ECDF91eF58541517e90D2` |
|
|
90
|
+
|
|
91
|
+
**Mainnet**
|
|
92
|
+
|
|
93
|
+
| Contract | Address |
|
|
94
|
+
| -------- | -------------------------------------------- |
|
|
95
|
+
| Signer | `0xCF9786F123F1071023dB8049808C223e94c384be` |
|
|
96
|
+
| Router | `0x1e878cCa765a8aAFEBecCa672c767441b4859634` |
|
|
97
|
+
| Payment | `0x0A497AC4261E37FA4062762C23Cf3cB642C839b8` |
|
|
35
98
|
|
|
36
99
|
## 1. Overview
|
|
37
100
|
|
|
@@ -42,7 +105,6 @@ The PMM integration with Optimex involves bidirectional API communication:
|
|
|
42
105
|
|
|
43
106
|
### 1.1. Integration Flow
|
|
44
107
|
|
|
45
|
-
|
|
46
108
|
```mermaid
|
|
47
109
|
sequenceDiagram
|
|
48
110
|
participant User
|
|
@@ -77,22 +139,24 @@ sequenceDiagram
|
|
|
77
139
|
|
|
78
140
|
### 2.1. API Environments
|
|
79
141
|
|
|
80
|
-
| Environment
|
|
81
|
-
|
|
|
82
|
-
| `dev`
|
|
83
|
-
| `staging`
|
|
84
|
-
| `prelive`
|
|
85
|
-
| `production`
|
|
142
|
+
| Environment | Description |
|
|
143
|
+
| ------------ | --------------------------------------------------------------------------- |
|
|
144
|
+
| `dev` | internal environment with test networks and development services |
|
|
145
|
+
| `staging` | Staging environment with test networks and staging services |
|
|
146
|
+
| `prelive` | Pre production environment with mainnet networks for testing before release |
|
|
147
|
+
| `production` | Production environment with mainnet networks and production services |
|
|
86
148
|
|
|
87
149
|
<details>
|
|
88
150
|
<summary><strong>Staging Contracts</strong></summary>
|
|
89
151
|
|
|
90
152
|
**Optimex L2 Testnet**
|
|
153
|
+
|
|
91
154
|
- **Signer**: [0xA89F5060B810F3b6027D7663880c43ee77A865C7](https://scan-testnet.optimex.xyz/address/0xA89F5060B810F3b6027D7663880c43ee77A865C7)
|
|
92
155
|
- **Router**: [0x31C88ebd9E430455487b6a5c8971e8eF63e97ED4](https://scan-testnet.optimex.xyz/address/0x31C88ebd9E430455487b6a5c8971e8eF63e97ED4)
|
|
93
156
|
- **ProtocolFetcherProxy**: [0x7c07151ca4DFd93F352Ab9B132A95866697c38c2](https://scan-testnet.optimex.xyz/address/0x7c07151ca4DFd93F352Ab9B132A95866697c38c2)
|
|
94
157
|
|
|
95
158
|
**Ethereum Sepolia**
|
|
159
|
+
|
|
96
160
|
- **Payment**: [0x7387DcCfE2f1D5F80b4ECDF91eF58541517e90D2](https://sepolia.etherscan.io/address/0x7387DcCfE2f1D5F80b4ECDF91eF58541517e90D2)
|
|
97
161
|
- **ETHVault**: [0x17aD543010fc8E8065b85E203839C0CBEcdfC851](https://sepolia.etherscan.io/address/0x17aD543010fc8E8065b85E203839C0CBEcdfC851)
|
|
98
162
|
- **WETHVault**: [0x673Ac1489457F43F04403940cE425ae19a9D639B](https://sepolia.etherscan.io/address/0x673Ac1489457F43F04403940cE425ae19a9D639B)
|
|
@@ -104,13 +168,14 @@ sequenceDiagram
|
|
|
104
168
|
<details>
|
|
105
169
|
<summary><strong>Production/Prelive Contracts</strong></summary>
|
|
106
170
|
|
|
107
|
-
|
|
108
171
|
**Optimex L2 Mainnet**
|
|
172
|
+
|
|
109
173
|
- **Signer**: [0xCF9786F123F1071023dB8049808C223e94c384be](https://scan.optimex.xyz/address/0xCF9786F123F1071023dB8049808C223e94c384be)
|
|
110
174
|
- **Router**: [0x1e878cCa765a8aAFEBecCa672c767441b4859634](https://scan.optimex.xyz/address/0x1e878cCa765a8aAFEBecCa672c767441b4859634)
|
|
111
175
|
- **ProtocolFetcherProxy**: [0xFDEd4CEf9aE1E03D0BeF161262a266c1c157a32b](https://scan.optimex.xyz/address/0xFDEd4CEf9aE1E03D0BeF161262a266c1c157a32b)
|
|
112
176
|
|
|
113
177
|
**Ethereum Mainnet**
|
|
178
|
+
|
|
114
179
|
- **Payment**: [0x0A497AC4261E37FA4062762C23Cf3cB642C839b8](https://etherscan.io/address/0x0A497AC4261E37FA4062762C23Cf3cB642C839b8)
|
|
115
180
|
- **ETHVault**: [0xF7fedF4A250157010807E6eA60258E3B768149Ff](https://etherscan.io/address/0xF7fedF4A250157010807E6eA60258E3B768149Ff)
|
|
116
181
|
- **WETHVault**: [0xaD3f379AaED8Eca895209Af446F2e34f07145dbC](https://etherscan.io/address/0xaD3f379AaED8Eca895209Af446F2e34f07145dbC)
|
|
@@ -175,54 +240,101 @@ GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=10000000000000000
|
|
|
175
240
|
<summary><strong>Example Implementation</strong></summary>
|
|
176
241
|
|
|
177
242
|
```js
|
|
243
|
+
import crypto from 'crypto'
|
|
244
|
+
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
245
|
+
|
|
246
|
+
// In-memory session storage (use Redis in production)
|
|
247
|
+
const sessionStore = new Map()
|
|
248
|
+
|
|
249
|
+
function generateSessionId() {
|
|
250
|
+
return crypto.randomBytes(16).toString('hex')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getPmmAddressByNetworkType(token) {
|
|
254
|
+
switch (token.networkType.toUpperCase()) {
|
|
255
|
+
case 'EVM':
|
|
256
|
+
return process.env.PMM_EVM_ADDRESS
|
|
257
|
+
case 'BTC':
|
|
258
|
+
case 'TBTC':
|
|
259
|
+
return process.env.PMM_BTC_ADDRESS
|
|
260
|
+
case 'SOLANA':
|
|
261
|
+
return process.env.PMM_SOLANA_ADDRESS
|
|
262
|
+
default:
|
|
263
|
+
throw new Error(`Unsupported network type: ${token.networkType}`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
178
267
|
async function getIndicativeQuote(req, res) {
|
|
179
268
|
try {
|
|
180
|
-
const { from_token_id, to_token_id, amount, session_id } = req.query
|
|
269
|
+
const { from_token_id, to_token_id, amount, session_id } = req.query
|
|
181
270
|
|
|
182
271
|
// Generate a session ID if not provided
|
|
183
|
-
const sessionId = session_id || generateSessionId()
|
|
184
|
-
|
|
185
|
-
// Fetch token information from Solver API
|
|
186
|
-
const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
|
|
187
|
-
const tokensData = await tokensResponse.json();
|
|
272
|
+
const sessionId = session_id || generateSessionId()
|
|
188
273
|
|
|
189
|
-
//
|
|
190
|
-
const fromToken =
|
|
191
|
-
|
|
274
|
+
// Fetch token information using SDK tokenService
|
|
275
|
+
const [fromToken, toToken] = await Promise.all([
|
|
276
|
+
tokenService.getTokenByTokenId(from_token_id),
|
|
277
|
+
tokenService.getTokenByTokenId(to_token_id),
|
|
278
|
+
])
|
|
192
279
|
|
|
193
|
-
if (!fromToken
|
|
280
|
+
if (!fromToken) {
|
|
281
|
+
return res.status(400).json({
|
|
282
|
+
session_id: sessionId,
|
|
283
|
+
pmm_receiving_address: '',
|
|
284
|
+
indicative_quote: '0',
|
|
285
|
+
error: `From token not found: ${from_token_id}`,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
if (!toToken) {
|
|
194
289
|
return res.status(400).json({
|
|
195
290
|
session_id: sessionId,
|
|
196
291
|
pmm_receiving_address: '',
|
|
197
292
|
indicative_quote: '0',
|
|
198
|
-
error:
|
|
199
|
-
})
|
|
293
|
+
error: `To token not found: ${to_token_id}`,
|
|
294
|
+
})
|
|
200
295
|
}
|
|
201
296
|
|
|
297
|
+
// Validate amount (implement your own validation logic)
|
|
298
|
+
const amountBigInt = BigInt(amount)
|
|
299
|
+
validateIndicativeAmount(amountBigInt, fromToken)
|
|
300
|
+
|
|
202
301
|
// Calculate the quote (implementation specific to your PMM)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
302
|
+
const quote = await calculateBestQuote({
|
|
303
|
+
amountIn: amount,
|
|
304
|
+
fromTokenId: from_token_id,
|
|
305
|
+
toTokenId: to_token_id,
|
|
306
|
+
isCommitment: false,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
// Get the receiving address based on network type
|
|
310
|
+
const pmmReceivingAddress = getPmmAddressByNetworkType(fromToken)
|
|
206
311
|
|
|
207
|
-
//
|
|
208
|
-
|
|
312
|
+
// Save session data for later use in commitment quote
|
|
313
|
+
sessionStore.set(sessionId, {
|
|
314
|
+
fromToken: from_token_id,
|
|
315
|
+
toToken: to_token_id,
|
|
316
|
+
amount: amount,
|
|
317
|
+
pmmReceivingAddress: pmmReceivingAddress,
|
|
318
|
+
indicativeQuote: quote,
|
|
319
|
+
})
|
|
209
320
|
|
|
210
321
|
return res.status(200).json({
|
|
211
322
|
session_id: sessionId,
|
|
212
323
|
pmm_receiving_address: pmmReceivingAddress,
|
|
213
324
|
indicative_quote: quote.toString(),
|
|
214
|
-
error: ''
|
|
215
|
-
})
|
|
325
|
+
error: '',
|
|
326
|
+
})
|
|
216
327
|
} catch (error) {
|
|
217
328
|
return res.status(500).json({
|
|
218
329
|
session_id: req.query.session_id || '',
|
|
219
330
|
pmm_receiving_address: '',
|
|
220
331
|
indicative_quote: '0',
|
|
221
|
-
error: error.message
|
|
222
|
-
})
|
|
332
|
+
error: error.message,
|
|
333
|
+
})
|
|
223
334
|
}
|
|
224
335
|
}
|
|
225
336
|
```
|
|
337
|
+
|
|
226
338
|
</details>
|
|
227
339
|
|
|
228
340
|
### 3.2. Endpoint: `/commitment-quote`
|
|
@@ -274,6 +386,11 @@ GET /commitment-quote?session_id=12345&trade_id=0x3bfe2fc4889a98a39b31b348e7b212
|
|
|
274
386
|
<summary><strong>Example Implementation</strong></summary>
|
|
275
387
|
|
|
276
388
|
```js
|
|
389
|
+
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
390
|
+
|
|
391
|
+
// Session store (use Redis in production)
|
|
392
|
+
const sessionStore = new Map()
|
|
393
|
+
|
|
277
394
|
async function getCommitmentQuote(req, res) {
|
|
278
395
|
try {
|
|
279
396
|
const {
|
|
@@ -287,73 +404,245 @@ async function getCommitmentQuote(req, res) {
|
|
|
287
404
|
user_deposit_tx,
|
|
288
405
|
user_deposit_vault,
|
|
289
406
|
trade_deadline,
|
|
290
|
-
script_deadline
|
|
291
|
-
} = req.query
|
|
407
|
+
script_deadline,
|
|
408
|
+
} = req.query
|
|
292
409
|
|
|
293
410
|
// Validate the session exists
|
|
294
|
-
const session =
|
|
411
|
+
const session = sessionStore.get(session_id)
|
|
295
412
|
if (!session) {
|
|
296
413
|
return res.status(400).json({
|
|
297
414
|
trade_id,
|
|
298
415
|
commitment_quote: '0',
|
|
299
|
-
error: 'Session
|
|
300
|
-
})
|
|
416
|
+
error: 'Session expired during processing',
|
|
417
|
+
})
|
|
301
418
|
}
|
|
302
419
|
|
|
303
|
-
// Fetch token information
|
|
304
|
-
const
|
|
305
|
-
|
|
420
|
+
// Fetch token information using SDK tokenService
|
|
421
|
+
const [fromToken, toToken] = await Promise.all([
|
|
422
|
+
tokenService.getTokenByTokenId(from_token_id),
|
|
423
|
+
tokenService.getTokenByTokenId(to_token_id),
|
|
424
|
+
])
|
|
306
425
|
|
|
307
|
-
|
|
308
|
-
const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
|
|
309
|
-
const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
|
|
310
|
-
|
|
311
|
-
if (!fromToken || !toToken) {
|
|
426
|
+
if (!fromToken) {
|
|
312
427
|
return res.status(400).json({
|
|
313
428
|
trade_id,
|
|
314
429
|
commitment_quote: '0',
|
|
315
|
-
error:
|
|
316
|
-
})
|
|
430
|
+
error: `From token not found: ${from_token_id}`,
|
|
431
|
+
})
|
|
317
432
|
}
|
|
433
|
+
if (!toToken) {
|
|
434
|
+
return res.status(400).json({
|
|
435
|
+
trade_id,
|
|
436
|
+
commitment_quote: '0',
|
|
437
|
+
error: `To token not found: ${to_token_id}`,
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Validate commitment amount (implement your own validation logic)
|
|
442
|
+
validateCommitmentAmount(BigInt(amount), fromToken)
|
|
443
|
+
|
|
444
|
+
// Delete any existing trade with the same ID (handle retries)
|
|
445
|
+
await tradeRepository.delete(trade_id)
|
|
318
446
|
|
|
319
447
|
// Calculate the final quote (implementation specific to your PMM)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
448
|
+
const quote = await calculateBestQuote({
|
|
449
|
+
amountIn: amount,
|
|
450
|
+
fromTokenId: from_token_id,
|
|
451
|
+
toTokenId: to_token_id,
|
|
452
|
+
isCommitment: true, // Use commitment pricing
|
|
453
|
+
})
|
|
323
454
|
|
|
324
455
|
// Store the trade in the database
|
|
325
456
|
await tradeRepository.create({
|
|
326
457
|
tradeId: trade_id,
|
|
327
|
-
sessionId: session_id,
|
|
328
458
|
fromTokenId: from_token_id,
|
|
329
459
|
toTokenId: to_token_id,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
460
|
+
fromUser: from_user_address,
|
|
461
|
+
toUser: to_user_address,
|
|
462
|
+
amount: amount,
|
|
463
|
+
fromNetworkId: fromToken.networkId,
|
|
464
|
+
toNetworkId: toToken.networkId,
|
|
333
465
|
userDepositTx: user_deposit_tx,
|
|
334
466
|
userDepositVault: user_deposit_vault,
|
|
335
467
|
tradeDeadline: trade_deadline,
|
|
336
468
|
scriptDeadline: script_deadline,
|
|
337
|
-
|
|
338
|
-
|
|
469
|
+
tradeType: 'SWAP',
|
|
470
|
+
commitmentQuote: quote.toString(),
|
|
471
|
+
})
|
|
339
472
|
|
|
340
473
|
return res.status(200).json({
|
|
341
474
|
trade_id,
|
|
342
475
|
commitment_quote: quote.toString(),
|
|
343
|
-
error: ''
|
|
344
|
-
})
|
|
476
|
+
error: '',
|
|
477
|
+
})
|
|
345
478
|
} catch (error) {
|
|
346
479
|
return res.status(500).json({
|
|
347
480
|
trade_id: req.query.trade_id || '',
|
|
348
481
|
commitment_quote: '0',
|
|
349
|
-
error: error.message
|
|
350
|
-
})
|
|
482
|
+
error: error.message,
|
|
483
|
+
})
|
|
351
484
|
}
|
|
352
485
|
}
|
|
353
486
|
```
|
|
487
|
+
|
|
354
488
|
</details>
|
|
355
489
|
|
|
356
|
-
### 3.3. Endpoint: `/
|
|
490
|
+
### 3.3. Endpoint: `/liquidation-quote`
|
|
491
|
+
|
|
492
|
+
#### Description
|
|
493
|
+
|
|
494
|
+
Provides a firm commitment quote for a liquidation trade. This endpoint is called after the user deposits funds and represents a binding commitment to execute the liquidation at the quoted rate.
|
|
495
|
+
|
|
496
|
+
#### Request Parameters
|
|
497
|
+
|
|
498
|
+
- **HTTP Method**: `GET`
|
|
499
|
+
- **Query Parameters**:
|
|
500
|
+
- `session_id` (string): Session identifier from the indicative quote.
|
|
501
|
+
- `trade_id` (string): Unique trade identifier.
|
|
502
|
+
- `from_token_id` (string): The ID of the source token.
|
|
503
|
+
- `to_token_id` (string): The ID of the destination token.
|
|
504
|
+
- `amount` (string): The amount of the source token to be traded, in base 10. This should be treated as a BigInt in your implementation.
|
|
505
|
+
- `payment_metadata` (string, optional): Hex string encoded data for smart contract payment method.
|
|
506
|
+
- `from_user_address` (string): The user's address from which the input token will be sent.
|
|
507
|
+
- `to_user_address` (string): The user's address to which the output token will be sent.
|
|
508
|
+
- `user_deposit_tx` (string): Transaction hash of user's deposit.
|
|
509
|
+
- `user_deposit_vault` (string): Vault containing user's deposit.
|
|
510
|
+
- `trade_deadline` (string): Expected payment deadline (UNIX timestamp).
|
|
511
|
+
- `script_deadline` (string): Withdrawal deadline if unpaid (UNIX timestamp).
|
|
512
|
+
|
|
513
|
+
#### Example Request
|
|
514
|
+
|
|
515
|
+
```
|
|
516
|
+
GET /liquidation-quote?session_id=12345&trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000&from_user_address=0xUserAddress&to_user_address=bc1p68q6hew27ljf4ghvlnwqz0fq32qg7tsgc7jr5levfy8r74p5k52qqphk07&user_deposit_tx=0xDepositTxHash&user_deposit_vault=VaultData&trade_deadline=1696012800&script_deadline=1696016400
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### Expected Response
|
|
520
|
+
|
|
521
|
+
- **HTTP Status**: `200 OK`
|
|
522
|
+
- **Response Body** (JSON):
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
|
|
527
|
+
"liquidation_quote": "987654321000000000",
|
|
528
|
+
"error": ""
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
- `trade_id` (string): The trade ID associated with the request.
|
|
533
|
+
- `liquidation_quote` (string): **Firm committed quote** - PMM must honor this price. Should be treated as a BigInt in your implementation.
|
|
534
|
+
- `error` (string): Error message, if any (empty if no error).
|
|
535
|
+
|
|
536
|
+
<details>
|
|
537
|
+
<summary><strong>Example Implementation</strong></summary>
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
541
|
+
|
|
542
|
+
// Session store (use Redis in production)
|
|
543
|
+
const sessionStore = new Map()
|
|
544
|
+
|
|
545
|
+
async function getLiquidationQuote(req, res) {
|
|
546
|
+
try {
|
|
547
|
+
const {
|
|
548
|
+
session_id,
|
|
549
|
+
trade_id,
|
|
550
|
+
from_token_id,
|
|
551
|
+
to_token_id,
|
|
552
|
+
amount,
|
|
553
|
+
payment_metadata,
|
|
554
|
+
from_user_address,
|
|
555
|
+
to_user_address,
|
|
556
|
+
user_deposit_tx,
|
|
557
|
+
user_deposit_vault,
|
|
558
|
+
trade_deadline,
|
|
559
|
+
script_deadline,
|
|
560
|
+
} = req.query
|
|
561
|
+
|
|
562
|
+
// Validate the session exists
|
|
563
|
+
const session = sessionStore.get(session_id)
|
|
564
|
+
if (!session) {
|
|
565
|
+
return res.status(400).json({
|
|
566
|
+
trade_id,
|
|
567
|
+
liquidation_quote: '0',
|
|
568
|
+
error: 'Session expired during processing',
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Fetch token information using SDK tokenService
|
|
573
|
+
const [fromToken, toToken] = await Promise.all([
|
|
574
|
+
tokenService.getTokenByTokenId(from_token_id),
|
|
575
|
+
tokenService.getTokenByTokenId(to_token_id),
|
|
576
|
+
])
|
|
577
|
+
|
|
578
|
+
if (!fromToken) {
|
|
579
|
+
return res.status(400).json({
|
|
580
|
+
trade_id,
|
|
581
|
+
liquidation_quote: '0',
|
|
582
|
+
error: `From token not found: ${from_token_id}`,
|
|
583
|
+
})
|
|
584
|
+
}
|
|
585
|
+
if (!toToken) {
|
|
586
|
+
return res.status(400).json({
|
|
587
|
+
trade_id,
|
|
588
|
+
liquidation_quote: '0',
|
|
589
|
+
error: `To token not found: ${to_token_id}`,
|
|
590
|
+
})
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Validate commitment amount (implement your own validation logic)
|
|
594
|
+
validateCommitmentAmount(BigInt(amount), fromToken)
|
|
595
|
+
|
|
596
|
+
// Delete any existing trade with the same ID (handle retries)
|
|
597
|
+
await tradeRepository.delete(trade_id)
|
|
598
|
+
|
|
599
|
+
// Calculate the firm liquidation quote (implementation specific to your PMM)
|
|
600
|
+
const quote = await calculateBestQuote({
|
|
601
|
+
amountIn: amount,
|
|
602
|
+
fromTokenId: from_token_id,
|
|
603
|
+
toTokenId: to_token_id,
|
|
604
|
+
isCommitment: true, // Use commitment pricing for liquidation
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// Store the trade in the database with LENDING trade type
|
|
608
|
+
await tradeRepository.create({
|
|
609
|
+
tradeId: trade_id,
|
|
610
|
+
fromTokenId: from_token_id,
|
|
611
|
+
toTokenId: to_token_id,
|
|
612
|
+
fromUser: from_user_address,
|
|
613
|
+
toUser: to_user_address,
|
|
614
|
+
amount: amount,
|
|
615
|
+
fromNetworkId: fromToken.networkId,
|
|
616
|
+
toNetworkId: toToken.networkId,
|
|
617
|
+
userDepositTx: user_deposit_tx,
|
|
618
|
+
userDepositVault: user_deposit_vault,
|
|
619
|
+
tradeDeadline: trade_deadline,
|
|
620
|
+
scriptDeadline: script_deadline,
|
|
621
|
+
tradeType: 'LENDING', // Liquidation trades use LENDING type
|
|
622
|
+
metadata: {
|
|
623
|
+
paymentMetadata: payment_metadata, // Store payment metadata for liquidation
|
|
624
|
+
},
|
|
625
|
+
commitmentQuote: quote.toString(),
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
return res.status(200).json({
|
|
629
|
+
trade_id,
|
|
630
|
+
liquidation_quote: quote.toString(),
|
|
631
|
+
error: '',
|
|
632
|
+
})
|
|
633
|
+
} catch (error) {
|
|
634
|
+
return res.status(500).json({
|
|
635
|
+
trade_id: req.query.trade_id || '',
|
|
636
|
+
liquidation_quote: '0',
|
|
637
|
+
error: error.message,
|
|
638
|
+
})
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
</details>
|
|
644
|
+
|
|
645
|
+
### 3.4. Endpoint: `/settlement-signature`
|
|
357
646
|
|
|
358
647
|
#### Description
|
|
359
648
|
|
|
@@ -397,69 +686,151 @@ GET /settlement-signature?trade_id=0x3d09b8eb94466bffa126aeda68c8c0f330633a7d005
|
|
|
397
686
|
<summary><strong>Example Implementation</strong></summary>
|
|
398
687
|
|
|
399
688
|
```js
|
|
689
|
+
import {
|
|
690
|
+
getCommitInfoHash,
|
|
691
|
+
getSignature,
|
|
692
|
+
routerService,
|
|
693
|
+
SignatureType,
|
|
694
|
+
signerService,
|
|
695
|
+
tokenService,
|
|
696
|
+
} from '@optimex-xyz/market-maker-sdk'
|
|
697
|
+
|
|
698
|
+
import { ethers } from 'ethers'
|
|
699
|
+
|
|
700
|
+
// Helper function to encode string to hex
|
|
701
|
+
const l2Encode = (info) => {
|
|
702
|
+
if (/^0x[0-9a-fA-F]*$/.test(info)) {
|
|
703
|
+
return info
|
|
704
|
+
}
|
|
705
|
+
return '0x' + Buffer.from(info, 'utf8').toString('hex')
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Helper function to decode hex to string
|
|
709
|
+
const l2Decode = (hex) => {
|
|
710
|
+
if (!hex.startsWith('0x')) return hex
|
|
711
|
+
return Buffer.from(hex.slice(2), 'hex').toString('utf8')
|
|
712
|
+
}
|
|
713
|
+
|
|
400
714
|
async function getSettlementSignature(req, res) {
|
|
401
715
|
try {
|
|
402
|
-
const { trade_id, committed_quote, trade_deadline, script_deadline } = req.query
|
|
716
|
+
const { trade_id, committed_quote, trade_deadline, script_deadline } = req.query
|
|
403
717
|
|
|
404
718
|
// Fetch the trade from the database
|
|
405
|
-
const trade = await tradeRepository.findById(trade_id)
|
|
719
|
+
const trade = await tradeRepository.findById(trade_id)
|
|
406
720
|
if (!trade) {
|
|
407
721
|
return res.status(400).json({
|
|
408
722
|
trade_id,
|
|
409
723
|
signature: '',
|
|
410
724
|
deadline: 0,
|
|
411
|
-
error: 'Trade not found'
|
|
412
|
-
})
|
|
725
|
+
error: 'Trade not found',
|
|
726
|
+
})
|
|
413
727
|
}
|
|
414
728
|
|
|
415
|
-
// Fetch trade
|
|
416
|
-
const
|
|
417
|
-
|
|
729
|
+
// Fetch presigns and trade data from router service
|
|
730
|
+
const [presigns, tradeData] = await Promise.all([
|
|
731
|
+
routerService.getSettlementPresigns(trade_id),
|
|
732
|
+
routerService.getTradeData(trade_id),
|
|
733
|
+
])
|
|
734
|
+
|
|
735
|
+
const { toChain, fromChain } = tradeData.tradeInfo
|
|
736
|
+
|
|
737
|
+
// Get the from token to determine PMM receiving address
|
|
738
|
+
const fromToken = await tokenService.getToken(l2Decode(fromChain[1]), l2Decode(fromChain[2]))
|
|
739
|
+
const pmmAddress = getPmmAddressByNetworkType(fromToken) // Your PMM address based on network type
|
|
418
740
|
|
|
419
741
|
// Calculate a deadline (30 minutes from now)
|
|
420
|
-
const deadline = Math.floor(Date.now() / 1000) + 1800
|
|
742
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800)
|
|
421
743
|
|
|
422
|
-
// Get PMM
|
|
423
|
-
const pmmId = process.env.PMM_ID
|
|
744
|
+
// Get PMM ID (should be hex encoded)
|
|
745
|
+
const pmmId = '0x' + Buffer.from(process.env.PMM_ID, 'utf8').toString('hex')
|
|
424
746
|
|
|
425
|
-
//
|
|
426
|
-
const
|
|
747
|
+
// Find PMM presign and validate receiving address
|
|
748
|
+
const pmmPresign = presigns.find((t) => t.pmmId === pmmId)
|
|
749
|
+
if (!pmmPresign) {
|
|
750
|
+
return res.status(400).json({
|
|
751
|
+
trade_id,
|
|
752
|
+
signature: '',
|
|
753
|
+
deadline: 0,
|
|
754
|
+
error: 'PMM presign not found',
|
|
755
|
+
})
|
|
756
|
+
}
|
|
427
757
|
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
758
|
+
// Validate that the presign receiving address matches expected PMM address
|
|
759
|
+
if (l2Decode(pmmPresign.pmmRecvAddress).toLowerCase() !== pmmAddress.toLowerCase()) {
|
|
760
|
+
return res.status(400).json({
|
|
761
|
+
trade_id,
|
|
762
|
+
signature: '',
|
|
763
|
+
deadline: 0,
|
|
764
|
+
error: 'PMM receiving address mismatch',
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const amountOut = BigInt(committed_quote)
|
|
769
|
+
|
|
770
|
+
// Create commitment info hash using SDK function
|
|
771
|
+
const commitInfoHash = getCommitInfoHash(
|
|
432
772
|
pmmId,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
773
|
+
l2Encode(pmmAddress),
|
|
774
|
+
toChain[1], // destination chain
|
|
775
|
+
toChain[2], // destination token address
|
|
776
|
+
amountOut,
|
|
437
777
|
deadline
|
|
438
|
-
)
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
// Get signer address and domain for EIP-712 signature
|
|
781
|
+
const signerAddress = await routerService.getSigner()
|
|
782
|
+
const domain = await signerService.getDomain()
|
|
783
|
+
|
|
784
|
+
// Set up provider and wallet
|
|
785
|
+
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL)
|
|
786
|
+
const pmmWallet = new ethers.Wallet(process.env.PMM_PRIVATE_KEY, provider)
|
|
439
787
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
|
|
788
|
+
// Generate signature using SDK function
|
|
789
|
+
const signature = await getSignature(
|
|
790
|
+
pmmWallet,
|
|
791
|
+
provider,
|
|
792
|
+
signerAddress,
|
|
793
|
+
trade_id,
|
|
794
|
+
commitInfoHash,
|
|
795
|
+
SignatureType.VerifyingContract,
|
|
796
|
+
domain
|
|
797
|
+
)
|
|
443
798
|
|
|
444
799
|
return res.status(200).json({
|
|
445
800
|
trade_id,
|
|
446
801
|
signature,
|
|
447
|
-
deadline,
|
|
448
|
-
error: ''
|
|
449
|
-
})
|
|
802
|
+
deadline: Number(deadline),
|
|
803
|
+
error: '',
|
|
804
|
+
})
|
|
450
805
|
} catch (error) {
|
|
451
806
|
return res.status(500).json({
|
|
452
807
|
trade_id: req.query.trade_id || '',
|
|
453
808
|
signature: '',
|
|
454
809
|
deadline: 0,
|
|
455
|
-
error: error.message
|
|
456
|
-
})
|
|
810
|
+
error: error.message,
|
|
811
|
+
})
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Helper function to get PMM address based on network type
|
|
816
|
+
function getPmmAddressByNetworkType(token) {
|
|
817
|
+
switch (token.networkType.toUpperCase()) {
|
|
818
|
+
case 'EVM':
|
|
819
|
+
return process.env.PMM_EVM_ADDRESS
|
|
820
|
+
case 'BTC':
|
|
821
|
+
case 'TBTC':
|
|
822
|
+
return process.env.PMM_BTC_ADDRESS
|
|
823
|
+
case 'SOLANA':
|
|
824
|
+
return process.env.PMM_SOLANA_ADDRESS
|
|
825
|
+
default:
|
|
826
|
+
throw new Error(`Unsupported network type: ${token.networkType}`)
|
|
457
827
|
}
|
|
458
828
|
}
|
|
459
829
|
```
|
|
830
|
+
|
|
460
831
|
</details>
|
|
461
832
|
|
|
462
|
-
### 3.
|
|
833
|
+
### 3.5. Endpoint: `/ack-settlement`
|
|
463
834
|
|
|
464
835
|
#### Description
|
|
465
836
|
|
|
@@ -504,44 +875,56 @@ trade_id=0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0&trad
|
|
|
504
875
|
<summary><strong>Example Implementation</strong></summary>
|
|
505
876
|
|
|
506
877
|
```js
|
|
878
|
+
// Trade status enum
|
|
879
|
+
const TradeStatus = {
|
|
880
|
+
PENDING: 'PENDING',
|
|
881
|
+
QUOTE_PROVIDED: 'QUOTE_PROVIDED',
|
|
882
|
+
COMMITTED: 'COMMITTED',
|
|
883
|
+
SELECTED: 'SELECTED',
|
|
884
|
+
SETTLING: 'SETTLING',
|
|
885
|
+
COMPLETED: 'COMPLETED',
|
|
886
|
+
FAILED: 'FAILED',
|
|
887
|
+
}
|
|
888
|
+
|
|
507
889
|
async function ackSettlement(req, res) {
|
|
508
890
|
try {
|
|
509
|
-
const { trade_id, trade_deadline, script_deadline, chosen } = req.body
|
|
891
|
+
const { trade_id, trade_deadline, script_deadline, chosen } = req.body
|
|
510
892
|
|
|
511
893
|
// Fetch the trade from the database
|
|
512
|
-
const trade = await tradeRepository.findById(trade_id)
|
|
894
|
+
const trade = await tradeRepository.findById(trade_id)
|
|
513
895
|
if (!trade) {
|
|
514
896
|
return res.status(400).json({
|
|
515
897
|
trade_id,
|
|
516
898
|
status: 'error',
|
|
517
|
-
error: 'Trade not found'
|
|
518
|
-
})
|
|
899
|
+
error: 'Trade not found',
|
|
900
|
+
})
|
|
519
901
|
}
|
|
520
902
|
|
|
521
|
-
// Update trade status based on whether
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
903
|
+
// Update trade status based on whether PMM was chosen
|
|
904
|
+
const isChosen = chosen === 'true'
|
|
905
|
+
const newStatus = isChosen ? TradeStatus.SELECTED : TradeStatus.FAILED
|
|
906
|
+
const failureReason = isChosen ? undefined : 'PMM not chosen for settlement'
|
|
907
|
+
|
|
908
|
+
await tradeRepository.updateStatus(trade_id, newStatus, failureReason)
|
|
527
909
|
|
|
528
910
|
return res.status(200).json({
|
|
529
911
|
trade_id,
|
|
530
912
|
status: 'acknowledged',
|
|
531
|
-
error: ''
|
|
532
|
-
})
|
|
913
|
+
error: '',
|
|
914
|
+
})
|
|
533
915
|
} catch (error) {
|
|
534
916
|
return res.status(500).json({
|
|
535
917
|
trade_id: req.body.trade_id || '',
|
|
536
918
|
status: 'error',
|
|
537
|
-
error: error.message
|
|
538
|
-
})
|
|
919
|
+
error: error.message,
|
|
920
|
+
})
|
|
539
921
|
}
|
|
540
922
|
}
|
|
541
923
|
```
|
|
924
|
+
|
|
542
925
|
</details>
|
|
543
926
|
|
|
544
|
-
### 3.
|
|
927
|
+
### 3.6. Endpoint: `/signal-payment`
|
|
545
928
|
|
|
546
929
|
#### Description
|
|
547
930
|
|
|
@@ -586,47 +969,90 @@ trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&tota
|
|
|
586
969
|
<summary><strong>Example Implementation</strong></summary>
|
|
587
970
|
|
|
588
971
|
```js
|
|
972
|
+
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
973
|
+
|
|
974
|
+
// Trade status enum
|
|
975
|
+
const TradeStatus = {
|
|
976
|
+
PENDING: 'PENDING',
|
|
977
|
+
QUOTE_PROVIDED: 'QUOTE_PROVIDED',
|
|
978
|
+
COMMITTED: 'COMMITTED',
|
|
979
|
+
SELECTED: 'SELECTED',
|
|
980
|
+
SETTLING: 'SETTLING',
|
|
981
|
+
COMPLETED: 'COMPLETED',
|
|
982
|
+
FAILED: 'FAILED',
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Queue names for different network types
|
|
986
|
+
const SETTLEMENT_QUEUES = {
|
|
987
|
+
EVM: 'settlement:evm:transfer',
|
|
988
|
+
BTC: 'settlement:btc:transfer',
|
|
989
|
+
SOLANA: 'settlement:solana:transfer',
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function getQueueNameByNetworkType(networkType) {
|
|
993
|
+
switch (networkType.toUpperCase()) {
|
|
994
|
+
case 'EVM':
|
|
995
|
+
return SETTLEMENT_QUEUES.EVM
|
|
996
|
+
case 'BTC':
|
|
997
|
+
case 'TBTC':
|
|
998
|
+
return SETTLEMENT_QUEUES.BTC
|
|
999
|
+
case 'SOLANA':
|
|
1000
|
+
return SETTLEMENT_QUEUES.SOLANA
|
|
1001
|
+
default:
|
|
1002
|
+
throw new Error(`Unsupported network type: ${networkType}`)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
589
1006
|
async function signalPayment(req, res) {
|
|
590
1007
|
try {
|
|
591
|
-
const { trade_id, total_fee_amount, trade_deadline, script_deadline } = req.body
|
|
1008
|
+
const { trade_id, total_fee_amount, trade_deadline, script_deadline } = req.body
|
|
592
1009
|
|
|
593
1010
|
// Fetch the trade from the database
|
|
594
|
-
const trade = await tradeRepository.findById(trade_id)
|
|
1011
|
+
const trade = await tradeRepository.findById(trade_id)
|
|
595
1012
|
if (!trade) {
|
|
596
1013
|
return res.status(400).json({
|
|
597
1014
|
trade_id,
|
|
598
1015
|
status: 'error',
|
|
599
|
-
error: 'Trade not found'
|
|
600
|
-
})
|
|
1016
|
+
error: 'Trade not found',
|
|
1017
|
+
})
|
|
601
1018
|
}
|
|
602
1019
|
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1020
|
+
// Validate trade status - must be SELECTED to proceed
|
|
1021
|
+
if (trade.status !== TradeStatus.SELECTED) {
|
|
1022
|
+
return res.status(400).json({
|
|
1023
|
+
trade_id,
|
|
1024
|
+
status: 'error',
|
|
1025
|
+
error: `Invalid trade status: ${trade.status}`,
|
|
1026
|
+
})
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Get the destination token to determine which queue to use
|
|
1030
|
+
const toToken = await tokenService.getTokenByTokenId(trade.toTokenId)
|
|
1031
|
+
const queueName = getQueueNameByNetworkType(toToken.networkType)
|
|
609
1032
|
|
|
610
|
-
// Queue the payment task
|
|
611
|
-
await paymentQueue.add({
|
|
1033
|
+
// Queue the payment task to the appropriate network queue
|
|
1034
|
+
await paymentQueue.add(queueName, {
|
|
612
1035
|
tradeId: trade_id,
|
|
613
|
-
|
|
614
|
-
|
|
1036
|
+
})
|
|
1037
|
+
|
|
1038
|
+
// Update trade status to SETTLING
|
|
1039
|
+
await tradeRepository.updateStatus(trade_id, TradeStatus.SETTLING)
|
|
615
1040
|
|
|
616
1041
|
return res.status(200).json({
|
|
617
1042
|
trade_id,
|
|
618
1043
|
status: 'acknowledged',
|
|
619
|
-
error: ''
|
|
620
|
-
})
|
|
1044
|
+
error: '',
|
|
1045
|
+
})
|
|
621
1046
|
} catch (error) {
|
|
622
1047
|
return res.status(500).json({
|
|
623
1048
|
trade_id: req.body.trade_id || '',
|
|
624
1049
|
status: 'error',
|
|
625
|
-
error: error.message
|
|
626
|
-
})
|
|
1050
|
+
error: error.message,
|
|
1051
|
+
})
|
|
627
1052
|
}
|
|
628
1053
|
}
|
|
629
1054
|
```
|
|
1055
|
+
|
|
630
1056
|
</details>
|
|
631
1057
|
|
|
632
1058
|
## 4. Solver API Endpoints for PMMs
|
|
@@ -661,79 +1087,81 @@ GET /v1/market-maker/tokens
|
|
|
661
1087
|
|
|
662
1088
|
```json
|
|
663
1089
|
{
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1090
|
+
"data": {
|
|
1091
|
+
"supported_networks": [
|
|
1092
|
+
{
|
|
1093
|
+
"network_id": "bitcoin_testnet",
|
|
1094
|
+
"name": "Bitcoin Testnet",
|
|
1095
|
+
"symbol": "tBTC",
|
|
1096
|
+
"type": "BTC",
|
|
1097
|
+
"logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg"
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
"network_id": "ethereum_sepolia",
|
|
1101
|
+
"name": "Ethereum Sepolia",
|
|
1102
|
+
"symbol": "ETH",
|
|
1103
|
+
"type": "EVM",
|
|
1104
|
+
"logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg"
|
|
1105
|
+
}
|
|
1106
|
+
],
|
|
1107
|
+
"tokens": [
|
|
1108
|
+
{
|
|
1109
|
+
"id": 2,
|
|
1110
|
+
"network_id": "bitcoin_testnet",
|
|
1111
|
+
"token_id": "tBTC",
|
|
1112
|
+
"network_name": "Bitcoin Testnet",
|
|
1113
|
+
"network_symbol": "tBTC",
|
|
1114
|
+
"network_type": "BTC",
|
|
1115
|
+
"token_name": "Bitcoin Testnet",
|
|
1116
|
+
"token_symbol": "tBTC",
|
|
1117
|
+
"token_address": "native",
|
|
1118
|
+
"token_decimals": 8,
|
|
1119
|
+
"token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/tbtc.svg",
|
|
1120
|
+
"network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg",
|
|
1121
|
+
"active": true,
|
|
1122
|
+
"created_at": "2024-10-28T07:24:33.179Z",
|
|
1123
|
+
"updated_at": "2024-11-07T04:40:46.454Z"
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
"id": 11,
|
|
1127
|
+
"network_id": "ethereum_sepolia",
|
|
1128
|
+
"token_id": "ETH",
|
|
1129
|
+
"network_name": "Ethereum Sepolia",
|
|
1130
|
+
"network_symbol": "ETH",
|
|
1131
|
+
"network_type": "EVM",
|
|
1132
|
+
"token_name": "Ethereum Sepolia",
|
|
1133
|
+
"token_symbol": "ETH",
|
|
1134
|
+
"token_address": "native",
|
|
1135
|
+
"token_decimals": 18,
|
|
1136
|
+
"token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth.svg",
|
|
1137
|
+
"network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg",
|
|
1138
|
+
"active": true,
|
|
1139
|
+
"created_at": "2024-11-22T08:36:59.175Z",
|
|
1140
|
+
"updated_at": "2024-11-22T08:36:59.175Z"
|
|
1141
|
+
}
|
|
1142
|
+
],
|
|
1143
|
+
"pairs": [
|
|
1144
|
+
{
|
|
1145
|
+
"from_token_id": "ETH",
|
|
1146
|
+
"to_token_id": "tBTC",
|
|
1147
|
+
"is_active": true
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
"from_token_id": "tBTC",
|
|
1151
|
+
"to_token_id": "ETH",
|
|
1152
|
+
"is_active": true
|
|
1153
|
+
}
|
|
1154
|
+
]
|
|
1155
|
+
}
|
|
730
1156
|
}
|
|
731
1157
|
```
|
|
1158
|
+
|
|
732
1159
|
</details>
|
|
733
1160
|
|
|
734
1161
|
### 4.2. Endpoint: `/v1/market-maker/submit-settlement-tx`
|
|
735
1162
|
|
|
736
1163
|
#### Description
|
|
1164
|
+
|
|
737
1165
|
Allows the PMM to submit settlement transaction hashes for trades. This endpoint is essential for completing the trade settlement process and must be called after making payments.
|
|
738
1166
|
|
|
739
1167
|
#### Request Parameters
|
|
@@ -759,17 +1187,18 @@ Allows the PMM to submit settlement transaction hashes for trades. This endpoint
|
|
|
759
1187
|
- `signed_at` (integer): UNIX timestamp (seconds) when you signed this submission.
|
|
760
1188
|
- `settlement_tx` (string): Should be hex format with a `0x` prefix
|
|
761
1189
|
|
|
762
|
-
|
|
763
|
-
- Use the transaction hash directly without additional encoding
|
|
764
|
-
- Example: `settlement_tx`: [0x7a87d2c423e13533b5ae0ecc5af900a7b697048103f4f6e32d19edde5e707355](https://etherscan.io/tx/0x7a87d2c423e13533b5ae0ecc5af900a7b697048103f4f6e32d19edde5e707355)
|
|
1190
|
+
- **For EVM Chains:**
|
|
765
1191
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1192
|
+
- Use the transaction hash directly without additional encoding
|
|
1193
|
+
- Example: `settlement_tx`: [0x7a87d2c423e13533b5ae0ecc5af900a7b697048103f4f6e32d19edde5e707355](https://etherscan.io/tx/0x7a87d2c423e13533b5ae0ecc5af900a7b697048103f4f6e32d19edde5e707355)
|
|
1194
|
+
|
|
1195
|
+
- **For Bitcoin or Solana:**
|
|
1196
|
+
- Must encode raw_tx string using the `l2Encode` function
|
|
1197
|
+
- Example raw_tx string: `3d83c7846d6e5b04279175a9592705a15373f3029b866d5224cc0744489fe403`
|
|
1198
|
+
- After encoding
|
|
1199
|
+
```
|
|
1200
|
+
"settlement_tx": "0x33643833633738343664366535623034323739313735613935393237303561313533373366333032396238363664353232346363303734343438396665343033"
|
|
1201
|
+
```
|
|
773
1202
|
|
|
774
1203
|
<details>
|
|
775
1204
|
<summary><strong>Bitcoin l2Encode</strong></summary>
|
|
@@ -789,6 +1218,7 @@ export const l2Encode = (info: string) => {
|
|
|
789
1218
|
return ensureHexPrefix(ethers.hexlify(toUtf8Bytes(info)))
|
|
790
1219
|
}
|
|
791
1220
|
```
|
|
1221
|
+
|
|
792
1222
|
</details>
|
|
793
1223
|
|
|
794
1224
|
#### Example Request
|
|
@@ -905,9 +1335,7 @@ GET /v1/market-maker/trades/0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9
|
|
|
905
1335
|
"user_deposit_tx": "0x202186375a3b8d55de4d8d1afb7f6a5bec8978cef3b705e6cb379729d03b16c7",
|
|
906
1336
|
"deposit_vault": "0xf7fedf4a250157010807e6ea60258e3b768149ff",
|
|
907
1337
|
"payment_bundle": {
|
|
908
|
-
"trade_ids": [
|
|
909
|
-
"0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9c7e51b670086c2"
|
|
910
|
-
],
|
|
1338
|
+
"trade_ids": ["0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9c7e51b670086c2"],
|
|
911
1339
|
"settlement_tx": "3d83c7846d6e5b04279175a9592705a15373f3029b866d5224cc0744489fe403",
|
|
912
1340
|
"signature": "0x479a5a89e7a871026b60307351ea650fc667890b25d3d02df7ed2e93f94db90d7c3f8dbd823220896b8ad49b13a90851199236e82a644ffbe99e53503929fe151b",
|
|
913
1341
|
"start_index": 0,
|
|
@@ -924,6 +1352,7 @@ GET /v1/market-maker/trades/0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9
|
|
|
924
1352
|
}
|
|
925
1353
|
}
|
|
926
1354
|
```
|
|
1355
|
+
|
|
927
1356
|
</details>
|
|
928
1357
|
|
|
929
1358
|
## 5. PMM Making Payment
|
|
@@ -933,53 +1362,47 @@ GET /v1/market-maker/trades/0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9
|
|
|
933
1362
|
In case the target chain is EVM-based, the transaction should emit the event from the `l1 payment contract` with the correct values for pmmAmountOut and protocolFee.
|
|
934
1363
|
|
|
935
1364
|
```js
|
|
936
|
-
const { ethers } = require('ethers')
|
|
1365
|
+
const { ethers } = require('ethers')
|
|
937
1366
|
|
|
938
1367
|
async function makeEVMPayment(tradeId, toAddress, amount, token, protocolFeeAmount) {
|
|
939
1368
|
try {
|
|
940
1369
|
// Get the private key from your secure storage
|
|
941
|
-
const privateKey = process.env.PMM_EVM_PRIVATE_KEY
|
|
1370
|
+
const privateKey = process.env.PMM_EVM_PRIVATE_KEY
|
|
942
1371
|
|
|
943
1372
|
// Set up the provider and signer
|
|
944
|
-
const rpcUrl = getRpcUrlForNetwork(token.networkId)
|
|
945
|
-
const provider = new ethers.JsonRpcProvider(rpcUrl)
|
|
946
|
-
const signer = new ethers.Wallet(privateKey, provider)
|
|
1373
|
+
const rpcUrl = getRpcUrlForNetwork(token.networkId)
|
|
1374
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl)
|
|
1375
|
+
const signer = new ethers.Wallet(privateKey, provider)
|
|
947
1376
|
|
|
948
1377
|
// Get the payment contract address
|
|
949
|
-
const paymentAddress = getPaymentAddressForNetwork(token.networkId)
|
|
1378
|
+
const paymentAddress = getPaymentAddressForNetwork(token.networkId)
|
|
950
1379
|
|
|
951
1380
|
// Create the contract instance
|
|
952
1381
|
const paymentAbi = [
|
|
953
1382
|
// ABI for the payment contract
|
|
954
|
-
|
|
955
|
-
]
|
|
956
|
-
const paymentContract = new ethers.Contract(paymentAddress, paymentAbi, signer)
|
|
1383
|
+
'function payment(bytes32 tradeId, address token, address recipient, uint256 amount, uint256 feeAmount, uint256 deadline) payable returns (bool)',
|
|
1384
|
+
]
|
|
1385
|
+
const paymentContract = new ethers.Contract(paymentAddress, paymentAbi, signer)
|
|
957
1386
|
|
|
958
1387
|
// Calculate the deadline (30 minutes from now)
|
|
959
|
-
const deadline = Math.floor(Date.now() / 1000) + 30 * 60
|
|
1388
|
+
const deadline = Math.floor(Date.now() / 1000) + 30 * 60
|
|
960
1389
|
|
|
961
1390
|
// If the token is native, we need to set the value
|
|
962
|
-
const value = token.tokenAddress === 'native' ? amount : 0
|
|
963
|
-
const tokenAddress = token.tokenAddress === 'native' ? ethers.ZeroAddress : token.tokenAddress
|
|
1391
|
+
const value = token.tokenAddress === 'native' ? amount : 0
|
|
1392
|
+
const tokenAddress = token.tokenAddress === 'native' ? ethers.ZeroAddress : token.tokenAddress
|
|
964
1393
|
|
|
965
1394
|
// Submit the transaction
|
|
966
|
-
const tx = await paymentContract.payment(
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
toAddress,
|
|
970
|
-
amount,
|
|
971
|
-
protocolFeeAmount,
|
|
972
|
-
deadline,
|
|
973
|
-
{ value }
|
|
974
|
-
);
|
|
1395
|
+
const tx = await paymentContract.payment(tradeId, tokenAddress, toAddress, amount, protocolFeeAmount, deadline, {
|
|
1396
|
+
value,
|
|
1397
|
+
})
|
|
975
1398
|
|
|
976
|
-
console.log(`Transfer transaction sent: ${tx.hash}`)
|
|
1399
|
+
console.log(`Transfer transaction sent: ${tx.hash}`)
|
|
977
1400
|
|
|
978
1401
|
// Return the transaction hash with the 0x prefix
|
|
979
|
-
return `0x${tx.hash.replace(/^0x/, '')}
|
|
1402
|
+
return `0x${tx.hash.replace(/^0x/, '')}`
|
|
980
1403
|
} catch (error) {
|
|
981
|
-
console.error('EVM payment error:', error)
|
|
982
|
-
throw error
|
|
1404
|
+
console.error('EVM payment error:', error)
|
|
1405
|
+
throw error
|
|
983
1406
|
}
|
|
984
1407
|
}
|
|
985
1408
|
```
|
|
@@ -989,11 +1412,12 @@ async function makeEVMPayment(tradeId, toAddress, amount, token, protocolFeeAmou
|
|
|
989
1412
|
In case the target chain is Bitcoin, the transaction should have at least N + 1 outputs, with the first N outputs being the settlement UTXOs for trades, and one of them being the change UTXO for the user with the correct amount. The output N + 1 is the OP_RETURN output with the hash of tradeIds.
|
|
990
1413
|
|
|
991
1414
|
```js
|
|
1415
|
+
import { getTradeIdsHash } from '@optimex-xyz/market-maker-sdk'
|
|
1416
|
+
|
|
1417
|
+
import axios from 'axios'
|
|
992
1418
|
import * as bitcoin from 'bitcoinjs-lib'
|
|
993
1419
|
import { ECPairFactory } from 'ecpair'
|
|
994
1420
|
import * as ecc from 'tiny-secp256k1'
|
|
995
|
-
import axios from 'axios'
|
|
996
|
-
import { getTradeIdsHash } from '@optimex-xyz/market-maker-sdk'
|
|
997
1421
|
|
|
998
1422
|
async function makeBitcoinPayment(params) {
|
|
999
1423
|
const { toAddress, amount, token, tradeId } = params
|
|
@@ -1050,9 +1474,7 @@ async function makeBitcoinPayment(params) {
|
|
|
1050
1474
|
|
|
1051
1475
|
// Check if we have enough balance
|
|
1052
1476
|
if (totalInput < amount) {
|
|
1053
|
-
throw new Error(
|
|
1054
|
-
`Insufficient balance. Need ${amount} satoshis, but only have ${totalInput} satoshis`
|
|
1055
|
-
)
|
|
1477
|
+
throw new Error(`Insufficient balance. Need ${amount} satoshis, but only have ${totalInput} satoshis`)
|
|
1056
1478
|
}
|
|
1057
1479
|
|
|
1058
1480
|
// Get fee rate
|
|
@@ -1077,10 +1499,7 @@ async function makeBitcoinPayment(params) {
|
|
|
1077
1499
|
// Add OP_RETURN output with trade ID hash
|
|
1078
1500
|
const tradeIdsHash = getTradeIdsHash([tradeId])
|
|
1079
1501
|
psbt.addOutput({
|
|
1080
|
-
script: bitcoin.script.compile([
|
|
1081
|
-
bitcoin.opcodes.OP_RETURN,
|
|
1082
|
-
Buffer.from(tradeIdsHash.slice(2), 'hex')
|
|
1083
|
-
]),
|
|
1502
|
+
script: bitcoin.script.compile([bitcoin.opcodes.OP_RETURN, Buffer.from(tradeIdsHash.slice(2), 'hex')]),
|
|
1084
1503
|
value: 0n,
|
|
1085
1504
|
})
|
|
1086
1505
|
|
|
@@ -1107,5 +1526,4 @@ async function makeBitcoinPayment(params) {
|
|
|
1107
1526
|
|
|
1108
1527
|
return response.data // Transaction ID
|
|
1109
1528
|
}
|
|
1110
|
-
|
|
1111
1529
|
```
|