@optimex-xyz/market-maker-sdk 0.5.0-dev-4237adc → 0.5.0-dev-aa5306f
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 +552 -511
- package/dist/index.d.mts +384 -7
- package/dist/index.d.ts +384 -7
- package/dist/index.js +117 -3
- package/dist/index.mjs +117 -3
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
# PMM
|
|
1
|
+
# PMM API Integration Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Note**: If you prefer using the SDK instead of direct API integration, please refer to the [PMM SDK Integration Guide](sdk-integration.md).
|
|
4
|
+
|
|
5
|
+
A comprehensive guide for implementing Private Market Makers (PMMs) in the cross-chain trading network. This documentation covers the required integration points between PMMs and our solver backend, enabling cross-chain liquidity provision and settlement.
|
|
4
6
|
|
|
5
7
|
## Table of Contents
|
|
6
8
|
|
|
7
|
-
- [PMM
|
|
9
|
+
- [PMM API Integration Documentation](#pmm-api-integration-documentation)
|
|
8
10
|
- [Table of Contents](#table-of-contents)
|
|
9
11
|
- [1. Overview](#1-overview)
|
|
10
|
-
- [1.1.
|
|
11
|
-
- [1.2. Example Implementation](#12-example-implementation)
|
|
12
|
+
- [1.1. Integration Flow](#11-integration-flow)
|
|
12
13
|
- [2. Quick Start](#2-quick-start)
|
|
13
|
-
- [2.1.
|
|
14
|
-
- [2.2. Environment Setup](#22-environment-setup)
|
|
15
|
-
- [2.3. Available Environments](#23-available-environments)
|
|
14
|
+
- [2.1. API Environments](#21-api-environments)
|
|
16
15
|
- [3. PMM Backend APIs](#3-pmm-backend-apis)
|
|
17
16
|
- [3.1. Endpoint: `/indicative-quote`](#31-endpoint-indicative-quote)
|
|
18
17
|
- [Description](#description)
|
|
@@ -44,21 +43,32 @@ A comprehensive toolkit for implementing Private Market Makers (PMMs) in the cro
|
|
|
44
43
|
- [Example Request](#example-request-4)
|
|
45
44
|
- [Expected Response](#expected-response-4)
|
|
46
45
|
- [Example Implementation](#example-implementation-4)
|
|
47
|
-
- [4.
|
|
48
|
-
- [4.1.
|
|
46
|
+
- [4. Solver API Endpoints for PMMs](#4-solver-api-endpoints-for-pmms)
|
|
47
|
+
- [4.1. Endpoint: `/v1/market-maker/tokens`](#41-endpoint-v1market-makertokens)
|
|
49
48
|
- [Description](#description-5)
|
|
50
|
-
- [
|
|
51
|
-
|
|
49
|
+
- [Request Parameters](#request-parameters-5)
|
|
50
|
+
- [Example Request](#example-request-5)
|
|
51
|
+
- [Expected Response](#expected-response-5)
|
|
52
|
+
- [4.2. Endpoint: `/v1/market-maker/submit-settlement-tx`](#42-endpoint-v1market-makersubmit-settlement-tx)
|
|
52
53
|
- [Description](#description-6)
|
|
53
|
-
- [
|
|
54
|
+
- [Request Parameters](#request-parameters-6)
|
|
55
|
+
- [Example Request](#example-request-6)
|
|
56
|
+
- [Expected Response](#expected-response-6)
|
|
54
57
|
- [Notes](#notes)
|
|
58
|
+
- [4.3. Endpoint: `/v1/market-maker/trades/:tradeId`](#43-endpoint-v1market-makertradestradeid)
|
|
59
|
+
- [Description](#description-7)
|
|
60
|
+
- [Request Parameters](#request-parameters-7)
|
|
61
|
+
- [Example Request](#example-request-7)
|
|
62
|
+
- [Expected Response](#expected-response-7)
|
|
55
63
|
- [5. PMM Making Payment](#5-pmm-making-payment)
|
|
56
64
|
- [5.1. EVM](#51-evm)
|
|
57
65
|
- [5.2. Bitcoin](#52-bitcoin)
|
|
58
66
|
|
|
59
67
|
## 1. Overview
|
|
60
68
|
|
|
61
|
-
This
|
|
69
|
+
This documentation contains everything needed to integrate your PMM with the solver network. The integration requires implementing specific API endpoints in your PMM service and making calls to the solver backend APIs.
|
|
70
|
+
|
|
71
|
+
### 1.1. Integration Flow
|
|
62
72
|
|
|
63
73
|
```mermaid
|
|
64
74
|
sequenceDiagram
|
|
@@ -86,44 +96,13 @@ sequenceDiagram
|
|
|
86
96
|
Solver->>PMM: POST /signal-payment
|
|
87
97
|
PMM-->>Solver: Acknowledge signal
|
|
88
98
|
PMM->>Chain: Execute settlement (transfer)
|
|
89
|
-
PMM->>Solver: POST /submit-settlement-tx
|
|
99
|
+
PMM->>Solver: POST /v1/market-maker/submit-settlement-tx
|
|
90
100
|
Solver-->>PMM: Confirm settlement submission
|
|
91
101
|
```
|
|
92
102
|
|
|
93
|
-
### 1.1. Repository Structure
|
|
94
|
-
The repository consists of:
|
|
95
|
-
- `abi/`: Smart contract ABIs and interfaces
|
|
96
|
-
- `example/`: A complete mock PMM implementation showing how to integrate the SDK
|
|
97
|
-
- `src/`: Source code for the market maker SDK
|
|
98
|
-
|
|
99
|
-
### 1.2. Example Implementation
|
|
100
|
-
The [Example](example/) directory contains a fully functional mock PMM. Use this implementation as a reference while integrating the `@optimex-xyz/market-maker-sdk` into your own PMM service.
|
|
101
|
-
|
|
102
103
|
## 2. Quick Start
|
|
103
104
|
|
|
104
|
-
### 2.1.
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
npm install @optimex-xyz/market-maker-sdk
|
|
108
|
-
# or
|
|
109
|
-
yarn add @optimex-xyz/market-maker-sdk
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 2.2. Environment Setup
|
|
113
|
-
|
|
114
|
-
you can directly specify the environment
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
import { sdk, Environment } from '@optimex-xyz/market-maker-sdk'
|
|
118
|
-
|
|
119
|
-
// Change to development environment
|
|
120
|
-
sdk.setEnvironment('dev' as Environment)
|
|
121
|
-
|
|
122
|
-
// Change to production environment
|
|
123
|
-
sdk.setEnvironment('production' as Environment)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 2.3. Available Environments
|
|
105
|
+
### 2.1. API Environments
|
|
127
106
|
|
|
128
107
|
| Environment | Description |
|
|
129
108
|
| ------------ | -------------------------------------------------------------------- |
|
|
@@ -148,13 +127,12 @@ Provides an indicative quote for the given token pair and trade amount. The quot
|
|
|
148
127
|
- **Query Parameters**:
|
|
149
128
|
- `from_token_id` (string): The ID of the source token.
|
|
150
129
|
- `to_token_id` (string): The ID of the destination token.
|
|
151
|
-
- `amount` (string): The amount of the source token to be traded, represented as a string in base 10 to accommodate large numbers.
|
|
152
|
-
- `session_id` (string, optional): A unique identifier for the session.
|
|
130
|
+
- `amount` (string): The amount of the source token to be traded, represented as a string in base 10 to accommodate large numbers. This should be treated as a BigInt in your implementation.
|
|
153
131
|
|
|
154
132
|
#### Example Request
|
|
155
133
|
|
|
156
134
|
```
|
|
157
|
-
GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000
|
|
135
|
+
GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000
|
|
158
136
|
```
|
|
159
137
|
|
|
160
138
|
#### Expected Response
|
|
@@ -173,40 +151,57 @@ GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=10000000000000000
|
|
|
173
151
|
|
|
174
152
|
- `session_id` (string): The session ID associated with the request.
|
|
175
153
|
- `pmm_receiving_address` (string): The receiving address where the user will send the `from_token`.
|
|
176
|
-
- `indicative_quote` (string): The indicative quote value, represented as a string.
|
|
154
|
+
- `indicative_quote` (string): The indicative quote value, represented as a string. Should be treated as a BigInt in your implementation.
|
|
177
155
|
- `error` (string): Error message, if any (empty if no error).
|
|
178
156
|
|
|
179
157
|
#### Example Implementation
|
|
180
158
|
|
|
181
159
|
```js
|
|
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
|
-
|
|
160
|
+
async function getIndicativeQuote(req, res) {
|
|
161
|
+
try {
|
|
162
|
+
const { from_token_id, to_token_id, amount, session_id } = req.query;
|
|
163
|
+
|
|
164
|
+
// Generate a session ID if not provided
|
|
165
|
+
const sessionId = session_id || generateSessionId();
|
|
166
|
+
|
|
167
|
+
// Fetch token information from Solver API
|
|
168
|
+
const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
|
|
169
|
+
const tokensData = await tokensResponse.json();
|
|
170
|
+
|
|
171
|
+
// Find the from token and to token
|
|
172
|
+
const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
|
|
173
|
+
const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
|
|
174
|
+
|
|
175
|
+
if (!fromToken || !toToken) {
|
|
176
|
+
return res.status(400).json({
|
|
177
|
+
session_id: sessionId,
|
|
178
|
+
pmm_receiving_address: '',
|
|
179
|
+
indicative_quote: '0',
|
|
180
|
+
error: 'Token not found'
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Calculate the quote (implementation specific to your PMM)
|
|
185
|
+
// Note: Treat amount as BigInt
|
|
186
|
+
const amountBigInt = BigInt(amount);
|
|
187
|
+
const quote = calculateQuote(fromToken, toToken, amountBigInt);
|
|
188
|
+
|
|
189
|
+
// Get the receiving address for this token pair
|
|
190
|
+
const pmmReceivingAddress = getPMMReceivingAddress(fromToken.network_id);
|
|
191
|
+
|
|
192
|
+
return res.status(200).json({
|
|
193
|
+
session_id: sessionId,
|
|
194
|
+
pmm_receiving_address: pmmReceivingAddress,
|
|
195
|
+
indicative_quote: quote.toString(),
|
|
196
|
+
error: ''
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return res.status(500).json({
|
|
200
|
+
session_id: req.query.session_id || '',
|
|
201
|
+
pmm_receiving_address: '',
|
|
202
|
+
indicative_quote: '0',
|
|
203
|
+
error: error.message
|
|
204
|
+
});
|
|
210
205
|
}
|
|
211
206
|
}
|
|
212
207
|
```
|
|
@@ -222,21 +217,21 @@ Provides a commitment quote for a specific trade, representing a firm commitment
|
|
|
222
217
|
- **HTTP Method**: `GET`
|
|
223
218
|
- **Query Parameters**:
|
|
224
219
|
- `session_id` (string): A unique identifier for the session.
|
|
225
|
-
- `trade_id` (string): The unique identifier for the trade.
|
|
220
|
+
- `trade_id` (string): The unique identifier for the trade. Example format: `0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328`.
|
|
226
221
|
- `from_token_id` (string): The ID of the source token.
|
|
227
222
|
- `to_token_id` (string): The ID of the destination token.
|
|
228
|
-
- `amount` (string): The amount of the source token to be traded, in base 10.
|
|
223
|
+
- `amount` (string): The amount of the source token to be traded, in base 10. This should be treated as a BigInt in your implementation.
|
|
229
224
|
- `from_user_address` (string): The address of the user initiating the trade.
|
|
230
225
|
- `to_user_address` (string): The address where the user will receive the `to_token`.
|
|
231
226
|
- `user_deposit_tx` (string): The transaction hash where the user deposited their funds.
|
|
232
227
|
- `user_deposit_vault` (string): The vault where the user's deposit is kept.
|
|
233
|
-
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
|
|
234
|
-
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
|
|
228
|
+
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment. Should be treated as a BigInt in your implementation.
|
|
229
|
+
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid. Should be treated as a BigInt in your implementation.
|
|
235
230
|
|
|
236
231
|
#### Example Request
|
|
237
232
|
|
|
238
233
|
```
|
|
239
|
-
GET /commitment-quote?session_id=12345&trade_id=
|
|
234
|
+
GET /commitment-quote?session_id=12345&trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000&from_user_address=0xUserAddress&to_user_address=0xReceivingAddress&user_deposit_tx=0xDepositTxHash&user_deposit_vault=VaultData&trade_deadline=1696012800&script_deadline=1696016400
|
|
240
235
|
```
|
|
241
236
|
|
|
242
237
|
#### Expected Response
|
|
@@ -246,57 +241,93 @@ GET /commitment-quote?session_id=12345&trade_id=abcd1234&from_token_id=ETH&to_to
|
|
|
246
241
|
|
|
247
242
|
```json
|
|
248
243
|
{
|
|
249
|
-
"trade_id": "
|
|
244
|
+
"trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
|
|
250
245
|
"commitment_quote": "987654321000000000",
|
|
251
246
|
"error": "" // Empty if no error
|
|
252
247
|
}
|
|
253
248
|
```
|
|
254
249
|
|
|
255
250
|
- `trade_id` (string): The trade ID associated with the request.
|
|
256
|
-
- `commitment_quote` (string): The committed quote value, represented as a string.
|
|
251
|
+
- `commitment_quote` (string): The committed quote value, represented as a string. Should be treated as a BigInt in your implementation.
|
|
257
252
|
- `error` (string): Error message, if any (empty if no error).
|
|
258
253
|
|
|
259
254
|
#### Example Implementation
|
|
260
255
|
|
|
261
256
|
```js
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
257
|
+
async function getCommitmentQuote(req, res) {
|
|
258
|
+
try {
|
|
259
|
+
const {
|
|
260
|
+
session_id,
|
|
261
|
+
trade_id,
|
|
262
|
+
from_token_id,
|
|
263
|
+
to_token_id,
|
|
264
|
+
amount,
|
|
265
|
+
from_user_address,
|
|
266
|
+
to_user_address,
|
|
267
|
+
user_deposit_tx,
|
|
268
|
+
user_deposit_vault,
|
|
269
|
+
trade_deadline,
|
|
270
|
+
script_deadline
|
|
271
|
+
} = req.query;
|
|
272
|
+
|
|
273
|
+
// Validate the session exists
|
|
274
|
+
const session = await sessionRepository.findById(session_id);
|
|
275
|
+
if (!session) {
|
|
276
|
+
return res.status(400).json({
|
|
277
|
+
trade_id,
|
|
278
|
+
commitment_quote: '0',
|
|
279
|
+
error: 'Session not found'
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Fetch token information from Solver API
|
|
284
|
+
const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
|
|
285
|
+
const tokensData = await tokensResponse.json();
|
|
286
|
+
|
|
287
|
+
// Find the from token and to token
|
|
288
|
+
const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
|
|
289
|
+
const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
|
|
290
|
+
|
|
291
|
+
if (!fromToken || !toToken) {
|
|
292
|
+
return res.status(400).json({
|
|
293
|
+
trade_id,
|
|
294
|
+
commitment_quote: '0',
|
|
295
|
+
error: 'Token not found'
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Calculate the final quote (implementation specific to your PMM)
|
|
300
|
+
// Note: Treat numeric values as BigInt
|
|
301
|
+
const amountBigInt = BigInt(amount);
|
|
302
|
+
const quote = calculateFinalQuote(fromToken, toToken, amountBigInt, trade_deadline);
|
|
303
|
+
|
|
304
|
+
// Store the trade in the database
|
|
305
|
+
await tradeRepository.create({
|
|
306
|
+
tradeId: trade_id,
|
|
307
|
+
sessionId: session_id,
|
|
308
|
+
fromTokenId: from_token_id,
|
|
309
|
+
toTokenId: to_token_id,
|
|
310
|
+
amount: amountBigInt.toString(),
|
|
311
|
+
fromUserAddress: from_user_address,
|
|
312
|
+
toUserAddress: to_user_address,
|
|
313
|
+
userDepositTx: user_deposit_tx,
|
|
314
|
+
userDepositVault: user_deposit_vault,
|
|
315
|
+
tradeDeadline: trade_deadline,
|
|
316
|
+
scriptDeadline: script_deadline,
|
|
317
|
+
commitmentQuote: quote.toString()
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return res.status(200).json({
|
|
321
|
+
trade_id,
|
|
322
|
+
commitment_quote: quote.toString(),
|
|
323
|
+
error: ''
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return res.status(500).json({
|
|
327
|
+
trade_id: req.query.trade_id || '',
|
|
328
|
+
commitment_quote: '0',
|
|
329
|
+
error: error.message
|
|
330
|
+
});
|
|
300
331
|
}
|
|
301
332
|
}
|
|
302
333
|
```
|
|
@@ -311,15 +342,16 @@ Returns a signature from the PMM to confirm the settlement quote, required to fi
|
|
|
311
342
|
|
|
312
343
|
- **HTTP Method**: `GET`
|
|
313
344
|
- **Query Parameters**:
|
|
314
|
-
- `trade_id` (string): The unique identifier for the trade.
|
|
315
|
-
- `committed_quote` (string): The committed quote value in base 10.
|
|
345
|
+
- `trade_id` (string): The unique identifier for the trade. Example format: `0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a`.
|
|
346
|
+
- `committed_quote` (string): The committed quote value in base 10. This should be treated as a BigInt in your implementation.
|
|
316
347
|
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
|
|
317
348
|
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
|
|
318
349
|
|
|
350
|
+
|
|
319
351
|
#### Example Request
|
|
320
352
|
|
|
321
353
|
```
|
|
322
|
-
GET /settlement-signature?trade_id=
|
|
354
|
+
GET /settlement-signature?trade_id=0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a&committed_quote=987654321000000000&trade_deadline=1696012800&script_deadline=1696016400
|
|
323
355
|
```
|
|
324
356
|
|
|
325
357
|
#### Expected Response
|
|
@@ -329,7 +361,7 @@ GET /settlement-signature?trade_id=abcd1234&committed_quote=987654321000000000&t
|
|
|
329
361
|
|
|
330
362
|
```json
|
|
331
363
|
{
|
|
332
|
-
"trade_id": "
|
|
364
|
+
"trade_id": "0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a",
|
|
333
365
|
"signature": "0xSignatureData",
|
|
334
366
|
"deadline": 1696012800,
|
|
335
367
|
"error": "" // Empty if no error
|
|
@@ -343,77 +375,64 @@ GET /settlement-signature?trade_id=abcd1234&committed_quote=987654321000000000&t
|
|
|
343
375
|
|
|
344
376
|
#### Example Implementation
|
|
345
377
|
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
getCommitInfoHash,
|
|
349
|
-
getSignature,
|
|
350
|
-
routerService,
|
|
351
|
-
SignatureType,
|
|
352
|
-
signerService,
|
|
353
|
-
} from '@optimex-xyz/market-maker-sdk'
|
|
354
|
-
|
|
355
|
-
export const GetSettlementSignatureSchema = z.object({
|
|
356
|
-
tradeId: z.string(),
|
|
357
|
-
committedQuote: z.string(),
|
|
358
|
-
tradeDeadline: z.string(),
|
|
359
|
-
scriptDeadline: z.string(),
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
export class GetSettlementSignatureDto extends createZodDto(GetSettlementSignatureSchema) {}
|
|
363
|
-
|
|
364
|
-
async getSettlementSignature(dto: GetSettlementSignatureDto, trade: Trade): Promise<SettlementSignatureResponseDto> {
|
|
378
|
+
```js
|
|
379
|
+
async function getSettlementSignature(req, res) {
|
|
365
380
|
try {
|
|
366
|
-
const {
|
|
381
|
+
const { trade_id, committed_quote, trade_deadline, script_deadline } = req.query;
|
|
382
|
+
|
|
383
|
+
// Fetch the trade from the database
|
|
384
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
385
|
+
if (!trade) {
|
|
386
|
+
return res.status(400).json({
|
|
387
|
+
trade_id,
|
|
388
|
+
signature: '',
|
|
389
|
+
deadline: 0,
|
|
390
|
+
error: 'Trade not found'
|
|
391
|
+
});
|
|
392
|
+
}
|
|
367
393
|
|
|
368
|
-
//
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
routerService.getTradeData(tradeId),
|
|
372
|
-
])
|
|
394
|
+
// Fetch trade details from Solver API
|
|
395
|
+
const tradeDetailsResponse = await fetch(`https://api.solver.example/v1/market-maker/trades/${trade_id}`);
|
|
396
|
+
const tradeDetails = await tradeDetailsResponse.json();
|
|
373
397
|
|
|
374
|
-
|
|
375
|
-
const deadline =
|
|
398
|
+
// Calculate a deadline (30 minutes from now)
|
|
399
|
+
const deadline = Math.floor(Date.now() / 1000) + 1800;
|
|
376
400
|
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
if (!pmmPresign) {
|
|
380
|
-
throw new BadRequestException('pmmPresign not found')
|
|
381
|
-
}
|
|
401
|
+
// Get PMM data
|
|
402
|
+
const pmmId = process.env.PMM_ID; // Your PMM ID
|
|
382
403
|
|
|
383
|
-
//
|
|
384
|
-
const
|
|
404
|
+
// Get the presigns and trade data from tradeDetails
|
|
405
|
+
const { from_token, to_token } = tradeDetails.data;
|
|
385
406
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
407
|
+
// Create a commitment info hash
|
|
408
|
+
// Note: Treat numeric values as BigInt
|
|
409
|
+
const committedQuoteBigInt = BigInt(committed_quote);
|
|
410
|
+
const commitInfoHash = createCommitInfoHash(
|
|
411
|
+
pmmId,
|
|
412
|
+
trade.pmmReceivingAddress,
|
|
413
|
+
to_token.chain,
|
|
414
|
+
to_token.address,
|
|
415
|
+
committedQuoteBigInt,
|
|
392
416
|
deadline
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
const signerAddress = await this.routerService.getSigner()
|
|
396
|
-
|
|
397
|
-
const domain = await signerService.getDomain()
|
|
417
|
+
);
|
|
398
418
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
signerAddress,
|
|
403
|
-
tradeId,
|
|
404
|
-
commitInfoHash,
|
|
405
|
-
SignatureType.VerifyingContract,
|
|
406
|
-
domain
|
|
407
|
-
)
|
|
419
|
+
// Sign the commitment with your private key
|
|
420
|
+
const privateKey = process.env.PMM_PRIVATE_KEY;
|
|
421
|
+
const signature = signMessage(privateKey, trade_id, commitInfoHash);
|
|
408
422
|
|
|
409
|
-
return {
|
|
410
|
-
|
|
423
|
+
return res.status(200).json({
|
|
424
|
+
trade_id,
|
|
411
425
|
signature,
|
|
412
|
-
deadline
|
|
413
|
-
error: ''
|
|
414
|
-
}
|
|
415
|
-
} catch (error
|
|
416
|
-
|
|
426
|
+
deadline,
|
|
427
|
+
error: ''
|
|
428
|
+
});
|
|
429
|
+
} catch (error) {
|
|
430
|
+
return res.status(500).json({
|
|
431
|
+
trade_id: req.query.trade_id || '',
|
|
432
|
+
signature: '',
|
|
433
|
+
deadline: 0,
|
|
434
|
+
error: error.message
|
|
435
|
+
});
|
|
417
436
|
}
|
|
418
437
|
}
|
|
419
438
|
```
|
|
@@ -428,7 +447,7 @@ Used by the solver to acknowledge to the PMM about a successful settlement, indi
|
|
|
428
447
|
|
|
429
448
|
- **HTTP Method**: `POST`
|
|
430
449
|
- **Form Parameters**:
|
|
431
|
-
- `trade_id` (string): The unique identifier for the trade.
|
|
450
|
+
- `trade_id` (string): The unique identifier for the trade. Example format: `0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0`.
|
|
432
451
|
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
|
|
433
452
|
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
|
|
434
453
|
- `chosen` (string): `"true"` if the PMM is selected, `"false"` otherwise.
|
|
@@ -439,7 +458,7 @@ Used by the solver to acknowledge to the PMM about a successful settlement, indi
|
|
|
439
458
|
POST /ack-settlement
|
|
440
459
|
Content-Type: application/x-www-form-urlencoded
|
|
441
460
|
|
|
442
|
-
trade_id=
|
|
461
|
+
trade_id=0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0&trade_deadline=1696012800&script_deadline=1696016400&chosen=true
|
|
443
462
|
```
|
|
444
463
|
|
|
445
464
|
#### Expected Response
|
|
@@ -449,7 +468,7 @@ trade_id=abcd1234&trade_deadline=1696012800&script_deadline=1696016400&chosen=tr
|
|
|
449
468
|
|
|
450
469
|
```json
|
|
451
470
|
{
|
|
452
|
-
"trade_id": "
|
|
471
|
+
"trade_id": "0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0",
|
|
453
472
|
"status": "acknowledged",
|
|
454
473
|
"error": "" // Empty if no error
|
|
455
474
|
}
|
|
@@ -461,30 +480,39 @@ trade_id=abcd1234&trade_deadline=1696012800&script_deadline=1696016400&chosen=tr
|
|
|
461
480
|
|
|
462
481
|
#### Example Implementation
|
|
463
482
|
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
483
|
+
```js
|
|
484
|
+
async function ackSettlement(req, res) {
|
|
485
|
+
try {
|
|
486
|
+
const { trade_id, trade_deadline, script_deadline, chosen } = req.body;
|
|
487
|
+
|
|
488
|
+
// Fetch the trade from the database
|
|
489
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
490
|
+
if (!trade) {
|
|
491
|
+
return res.status(400).json({
|
|
492
|
+
trade_id,
|
|
493
|
+
status: 'error',
|
|
494
|
+
error: 'Trade not found'
|
|
495
|
+
});
|
|
496
|
+
}
|
|
473
497
|
|
|
474
|
-
|
|
498
|
+
// Update trade status based on whether it was chosen
|
|
499
|
+
await tradeRepository.update(trade_id, {
|
|
500
|
+
chosen: chosen === 'true',
|
|
501
|
+
tradeDeadline: trade_deadline,
|
|
502
|
+
scriptDeadline: script_deadline
|
|
503
|
+
});
|
|
475
504
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
tradeId: dto.tradeId,
|
|
505
|
+
return res.status(200).json({
|
|
506
|
+
trade_id,
|
|
480
507
|
status: 'acknowledged',
|
|
481
|
-
error: ''
|
|
482
|
-
}
|
|
483
|
-
} catch (error
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
508
|
+
error: ''
|
|
509
|
+
});
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return res.status(500).json({
|
|
512
|
+
trade_id: req.body.trade_id || '',
|
|
513
|
+
status: 'error',
|
|
514
|
+
error: error.message
|
|
515
|
+
});
|
|
488
516
|
}
|
|
489
517
|
}
|
|
490
518
|
```
|
|
@@ -499,10 +527,10 @@ Used by the solver to signal the chosen PMM to start submitting their payment.
|
|
|
499
527
|
|
|
500
528
|
- **HTTP Method**: `POST`
|
|
501
529
|
- **Form Parameters**:
|
|
502
|
-
- `trade_id` (string): The unique identifier for the trade.
|
|
503
|
-
- `protocol_fee_amount` (string): The amount of protocol fee the PMM has to submit, in base 10.
|
|
530
|
+
- `trade_id` (string): The unique identifier for the trade. Example format: `0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328`.
|
|
504
531
|
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
|
|
505
532
|
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
|
|
533
|
+
- `total_fee_amount` (string): The amount of total fee the PMM has to submit, in base 10. This should be treated as a BigInt in your implementation.
|
|
506
534
|
|
|
507
535
|
#### Example Request
|
|
508
536
|
|
|
@@ -510,7 +538,7 @@ Used by the solver to signal the chosen PMM to start submitting their payment.
|
|
|
510
538
|
POST /signal-payment
|
|
511
539
|
Content-Type: application/x-www-form-urlencoded
|
|
512
540
|
|
|
513
|
-
trade_id=
|
|
541
|
+
trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&total_fee_amount=1000000000000000&trade_deadline=1696012800&script_deadline=1696016400
|
|
514
542
|
```
|
|
515
543
|
|
|
516
544
|
#### Expected Response
|
|
@@ -520,7 +548,7 @@ trade_id=abcd1234&protocol_fee_amount=1000000000000000&trade_deadline=1696012800
|
|
|
520
548
|
|
|
521
549
|
```json
|
|
522
550
|
{
|
|
523
|
-
"trade_id": "
|
|
551
|
+
"trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
|
|
524
552
|
"status": "acknowledged",
|
|
525
553
|
"error": "" // Empty if no error
|
|
526
554
|
}
|
|
@@ -532,121 +560,202 @@ trade_id=abcd1234&protocol_fee_amount=1000000000000000&trade_deadline=1696012800
|
|
|
532
560
|
|
|
533
561
|
#### Example Implementation
|
|
534
562
|
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
tradeId: z.string(),
|
|
538
|
-
protocolFeeAmount: z.string(),
|
|
539
|
-
tradeDeadline: z.string(),
|
|
540
|
-
scriptDeadline: z.string(),
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
export class SignalPaymentDto extends createZodDto(SignalPaymentSchema) {}
|
|
544
|
-
|
|
545
|
-
async signalPayment(dto: SignalPaymentDto, trade: Trade): Promise<SignalPaymentResponseDto> {
|
|
563
|
+
```js
|
|
564
|
+
async function signalPayment(req, res) {
|
|
546
565
|
try {
|
|
547
|
-
|
|
566
|
+
const { trade_id, total_fee_amount, trade_deadline, script_deadline } = req.body;
|
|
567
|
+
|
|
568
|
+
// Fetch the trade from the database
|
|
569
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
570
|
+
if (!trade) {
|
|
571
|
+
return res.status(400).json({
|
|
572
|
+
trade_id,
|
|
573
|
+
status: 'error',
|
|
574
|
+
error: 'Trade not found'
|
|
575
|
+
});
|
|
576
|
+
}
|
|
548
577
|
|
|
549
|
-
|
|
550
|
-
|
|
578
|
+
// Update trade with fee amount
|
|
579
|
+
await tradeRepository.update(trade_id, {
|
|
580
|
+
totalFeeAmount: total_fee_amount,
|
|
581
|
+
tradeDeadline: trade_deadline,
|
|
582
|
+
scriptDeadline: script_deadline
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Queue the payment task
|
|
586
|
+
await paymentQueue.add({
|
|
587
|
+
tradeId: trade_id,
|
|
588
|
+
totalFeeAmount: total_fee_amount
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return res.status(200).json({
|
|
592
|
+
trade_id,
|
|
551
593
|
status: 'acknowledged',
|
|
552
|
-
error: ''
|
|
553
|
-
}
|
|
554
|
-
} catch (error
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
594
|
+
error: ''
|
|
595
|
+
});
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return res.status(500).json({
|
|
598
|
+
trade_id: req.body.trade_id || '',
|
|
599
|
+
status: 'error',
|
|
600
|
+
error: error.message
|
|
601
|
+
});
|
|
559
602
|
}
|
|
560
603
|
}
|
|
561
604
|
```
|
|
562
605
|
|
|
563
|
-
## 4.
|
|
606
|
+
## 4. Solver API Endpoints for PMMs
|
|
607
|
+
|
|
564
608
|
|
|
565
|
-
These
|
|
609
|
+
These API endpoints are provided by the Solver backend for PMMs to retrieve token information and submit settlement data.
|
|
566
610
|
|
|
567
|
-
|
|
611
|
+
> **Note**: The base URL for the Solver API endpoints will be provided separately. All endpoint paths in this documentation should be appended to that base URL.
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
### 4.1. Endpoint: `/v1/market-maker/tokens`
|
|
568
615
|
|
|
569
616
|
#### Description
|
|
570
617
|
|
|
571
|
-
Returns a list of
|
|
618
|
+
Returns a list of tokens supported by the Solver Backend.
|
|
619
|
+
|
|
620
|
+
#### Request Parameters
|
|
621
|
+
|
|
622
|
+
- **HTTP Method**: `GET`
|
|
623
|
+
|
|
624
|
+
#### Example Request
|
|
572
625
|
|
|
573
|
-
|
|
626
|
+
```
|
|
627
|
+
GET /v1/market-maker/tokens
|
|
628
|
+
```
|
|
574
629
|
|
|
575
|
-
|
|
576
|
-
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
630
|
+
#### Expected Response
|
|
577
631
|
|
|
578
|
-
|
|
632
|
+
- **HTTP Status**: `200 OK`
|
|
633
|
+
- **Response Body** (JSON):
|
|
634
|
+
|
|
635
|
+
```json
|
|
636
|
+
{
|
|
637
|
+
"data": {
|
|
638
|
+
"supported_networks": [
|
|
639
|
+
{
|
|
640
|
+
"network_id": "bitcoin_testnet",
|
|
641
|
+
"name": "Bitcoin Testnet",
|
|
642
|
+
"symbol": "tBTC",
|
|
643
|
+
"type": "BTC",
|
|
644
|
+
"logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/btc_network.svg"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
"network_id": "ethereum_sepolia",
|
|
648
|
+
"name": "Ethereum Sepolia",
|
|
649
|
+
"symbol": "ETH",
|
|
650
|
+
"type": "EVM",
|
|
651
|
+
"logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth_network.svg"
|
|
652
|
+
}
|
|
653
|
+
],
|
|
654
|
+
"tokens": [
|
|
655
|
+
{
|
|
656
|
+
"id": 2,
|
|
657
|
+
"network_id": "bitcoin_testnet",
|
|
658
|
+
"token_id": "tBTC",
|
|
659
|
+
"network_name": "Bitcoin Testnet",
|
|
660
|
+
"network_symbol": "tBTC",
|
|
661
|
+
"network_type": "BTC",
|
|
662
|
+
"token_name": "Bitcoin Testnet",
|
|
663
|
+
"token_symbol": "tBTC",
|
|
664
|
+
"token_address": "native",
|
|
665
|
+
"token_decimals": 8,
|
|
666
|
+
"token_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/tbtc.svg",
|
|
667
|
+
"network_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/btc_network.svg",
|
|
668
|
+
"active": true,
|
|
669
|
+
"created_at": "2024-10-28T07:24:33.179Z",
|
|
670
|
+
"updated_at": "2024-11-07T04:40:46.454Z"
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"id": 11,
|
|
674
|
+
"network_id": "ethereum_sepolia",
|
|
675
|
+
"token_id": "ETH",
|
|
676
|
+
"network_name": "Ethereum Sepolia",
|
|
677
|
+
"network_symbol": "ETH",
|
|
678
|
+
"network_type": "EVM",
|
|
679
|
+
"token_name": "Ethereum Sepolia",
|
|
680
|
+
"token_symbol": "ETH",
|
|
681
|
+
"token_address": "native",
|
|
682
|
+
"token_decimals": 18,
|
|
683
|
+
"token_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth.svg",
|
|
684
|
+
"network_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth_network.svg",
|
|
685
|
+
"active": true,
|
|
686
|
+
"created_at": "2024-11-22T08:36:59.175Z",
|
|
687
|
+
"updated_at": "2024-11-22T08:36:59.175Z"
|
|
688
|
+
}
|
|
689
|
+
],
|
|
690
|
+
"pairs": [
|
|
691
|
+
{
|
|
692
|
+
"from_token_id": "ETH",
|
|
693
|
+
"to_token_id": "tBTC",
|
|
694
|
+
"is_active": true
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
"from_token_id": "tBTC",
|
|
698
|
+
"to_token_id": "ETH",
|
|
699
|
+
"is_active": true
|
|
700
|
+
}
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
}
|
|
579
704
|
```
|
|
580
705
|
|
|
581
|
-
### 4.2.
|
|
706
|
+
### 4.2. Endpoint: `/v1/market-maker/submit-settlement-tx`
|
|
582
707
|
|
|
583
708
|
#### Description
|
|
584
709
|
|
|
585
710
|
Allows the PMM to submit the settlement transaction hash for one or more trades. This step is necessary to complete the trade settlement process.
|
|
586
711
|
|
|
587
|
-
Parameters
|
|
712
|
+
#### Request Parameters
|
|
713
|
+
|
|
714
|
+
- **HTTP Method**: `POST`
|
|
715
|
+
- **Request Body** (JSON):
|
|
716
|
+
|
|
717
|
+
```json
|
|
718
|
+
{
|
|
719
|
+
"trade_ids": ["0xTradeID1", "0xTradeID2", "..."],
|
|
720
|
+
"pmm_id": "pmm001",
|
|
721
|
+
"settlement_tx": "SettlementTransactionData",
|
|
722
|
+
"signature": "0xSignatureData",
|
|
723
|
+
"start_index": 0,
|
|
724
|
+
"signed_at": 1719158400
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
588
728
|
- `trade_ids` (array of strings): An array of trade IDs associated with the settlement transaction.
|
|
589
729
|
- `pmm_id` (string): The PMM's ID, which must match the one committed for the trade(s).
|
|
590
|
-
- `settlement_tx` (string): The
|
|
730
|
+
- `settlement_tx` (string): The txHash of the settlement.
|
|
591
731
|
- `signature` (string): The PMM's signature on the settlement transaction.
|
|
592
732
|
- `start_index` (integer): The index indicating the starting point for settlement processing (used for batch settlements).
|
|
593
733
|
- `signed_at` (integer): The UNIX timestamp (in seconds) when the PMM signed the settlement transaction.
|
|
594
734
|
|
|
595
|
-
#### Example
|
|
596
|
-
|
|
597
|
-
```ts
|
|
598
|
-
import {
|
|
599
|
-
getMakePaymentHash,
|
|
600
|
-
getSignature,
|
|
601
|
-
routerService,
|
|
602
|
-
SignatureType,
|
|
603
|
-
signerService,
|
|
604
|
-
solverService,
|
|
605
|
-
} from '@optimex-xyz/market-maker-sdk'
|
|
606
|
-
|
|
607
|
-
async submit(job: Job<string>) {
|
|
608
|
-
const { tradeId, paymentTxId } = toObject(job.data) as SubmitSettlementEvent
|
|
609
|
-
|
|
610
|
-
try {
|
|
611
|
-
const tradeIds: BytesLike[] = [tradeId]
|
|
612
|
-
const startIdx = BigInt(tradeIds.indexOf(tradeId))
|
|
613
|
-
|
|
614
|
-
const signerAddress = await this.routerService.getSigner()
|
|
615
|
-
|
|
616
|
-
const signedAt = Math.floor(Date.now() / 1000)
|
|
617
|
-
|
|
618
|
-
const makePaymentInfoHash = getMakePaymentHash(tradeIds, BigInt(signedAt), startIdx, ensureHexPrefix(paymentTxId))
|
|
619
|
-
|
|
620
|
-
const domain = await signerService.getDomain()
|
|
735
|
+
#### Example Request
|
|
621
736
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
signerAddress,
|
|
626
|
-
tradeId,
|
|
627
|
-
makePaymentInfoHash,
|
|
628
|
-
SignatureType.MakePayment,
|
|
629
|
-
domain
|
|
630
|
-
)
|
|
737
|
+
```
|
|
738
|
+
POST /v1/market-maker/submit-settlement-tx
|
|
739
|
+
Content-Type: application/json
|
|
631
740
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
741
|
+
{
|
|
742
|
+
"trade_ids": ["0xabcdef123456...", "0x123456abcdef..."],
|
|
743
|
+
"pmm_id": "pmm001",
|
|
744
|
+
"settlement_tx": "0xRawTransactionData",
|
|
745
|
+
"signature": "0xSignatureData",
|
|
746
|
+
"start_index": 0,
|
|
747
|
+
"signed_at": 1719158400
|
|
748
|
+
}
|
|
749
|
+
```
|
|
641
750
|
|
|
642
|
-
|
|
751
|
+
#### Expected Response
|
|
643
752
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
this.logger.error('submit settlement error', error.stack)
|
|
753
|
+
- **HTTP Status**: `200 OK`
|
|
754
|
+
- **Response Body** (JSON):
|
|
647
755
|
|
|
648
|
-
|
|
649
|
-
|
|
756
|
+
```json
|
|
757
|
+
{
|
|
758
|
+
"message": "Settlement transaction submitted successfully"
|
|
650
759
|
}
|
|
651
760
|
```
|
|
652
761
|
|
|
@@ -656,100 +765,85 @@ async submit(job: Job<string>) {
|
|
|
656
765
|
- **Start Index**: Used when submitting a batch of settlements to indicate the position within the batch.
|
|
657
766
|
- **Signature**: Must be valid and verifiable by the solver backend.
|
|
658
767
|
|
|
659
|
-
|
|
768
|
+
### 4.3. Endpoint: `/v1/market-maker/trades/:tradeId`
|
|
660
769
|
|
|
661
|
-
|
|
662
|
-
import { Token } from '@optimex-xyz/market-maker-sdk'
|
|
770
|
+
#### Description
|
|
663
771
|
|
|
664
|
-
|
|
665
|
-
toAddress: string
|
|
666
|
-
amount: bigint
|
|
667
|
-
token: Token
|
|
668
|
-
tradeId: string
|
|
669
|
-
}
|
|
772
|
+
Returns detailed information about a specific trade by its trade ID. This endpoint allows PMMs to fetch comprehensive data about a trade, including token information, user addresses, quotes, settlement details, and current state.
|
|
670
773
|
|
|
671
|
-
|
|
672
|
-
transfer(params: TransferParams): Promise<string>
|
|
673
|
-
}
|
|
674
|
-
```
|
|
774
|
+
#### Request Parameters
|
|
675
775
|
|
|
676
|
-
|
|
776
|
+
- **HTTP Method**: `GET`
|
|
777
|
+
- **Path Parameters**:
|
|
778
|
+
- `tradeId` (string): The unique identifier for the trade to retrieve.
|
|
677
779
|
|
|
678
|
-
|
|
780
|
+
#### Example Request
|
|
679
781
|
|
|
680
|
-
|
|
782
|
+
```
|
|
783
|
+
GET /v1/market-maker/trades/0x650e2c921a85eb0b8831ff838d4d98c0a5cd2ede5c0cb6bb4a15969f51c75424
|
|
784
|
+
```
|
|
681
785
|
|
|
682
|
-
|
|
683
|
-
import { config, ensureHexPrefix, ERC20__factory, Payment__factory, routerService } from '@optimex-xyz/market-maker-sdk'
|
|
786
|
+
#### Expected Response
|
|
684
787
|
|
|
685
|
-
|
|
788
|
+
- **HTTP Status**: `200 OK`
|
|
789
|
+
- **Response Body** (JSON): See the detailed response format in the updated PMM Integration API Documentation.
|
|
686
790
|
|
|
687
|
-
|
|
688
|
-
export class EVMTransferStrategy implements ITransferStrategy {
|
|
689
|
-
private pmmPrivateKey: string
|
|
791
|
+
## 5. PMM Making Payment
|
|
690
792
|
|
|
691
|
-
|
|
692
|
-
private readonly rpcMap = new Map<string, string>([['ethereum_sepolia', 'https://eth-sepolia.public.blastapi.io']])
|
|
793
|
+
### 5.1. EVM
|
|
693
794
|
|
|
694
|
-
|
|
695
|
-
this.pmmPrivateKey = this.configService.getOrThrow<string>('PMM_EVM_PRIVATE_KEY')
|
|
696
|
-
}
|
|
795
|
+
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.
|
|
697
796
|
|
|
698
|
-
|
|
699
|
-
const { toAddress, amount, token, tradeId } = params
|
|
700
|
-
const { tokenAddress, networkId } = token
|
|
797
|
+
Example implementation:
|
|
701
798
|
|
|
702
|
-
|
|
799
|
+
```js
|
|
800
|
+
const { ethers } = require('ethers');
|
|
703
801
|
|
|
704
|
-
|
|
802
|
+
async function makeEVMPayment(tradeId, toAddress, amount, token, protocolFeeAmount) {
|
|
803
|
+
try {
|
|
804
|
+
// Get the private key from your secure storage
|
|
805
|
+
const privateKey = process.env.PMM_EVM_PRIVATE_KEY;
|
|
705
806
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
807
|
+
// Set up the provider and signer
|
|
808
|
+
const rpcUrl = getRpcUrlForNetwork(token.networkId);
|
|
809
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
810
|
+
const signer = new ethers.Wallet(privateKey, provider);
|
|
709
811
|
|
|
710
|
-
|
|
812
|
+
// Get the payment contract address
|
|
813
|
+
const paymentAddress = getPaymentAddressForNetwork(token.networkId);
|
|
711
814
|
|
|
712
|
-
|
|
815
|
+
// Create the contract instance
|
|
816
|
+
const paymentAbi = [
|
|
817
|
+
// ABI for the payment contract
|
|
818
|
+
"function payment(bytes32 tradeId, address token, address recipient, uint256 amount, uint256 feeAmount, uint256 deadline) payable returns (bool)"
|
|
819
|
+
];
|
|
820
|
+
const paymentContract = new ethers.Contract(paymentAddress, paymentAbi, signer);
|
|
713
821
|
|
|
714
|
-
|
|
822
|
+
// Calculate the deadline (30 minutes from now)
|
|
823
|
+
const deadline = Math.floor(Date.now() / 1000) + 30 * 60;
|
|
715
824
|
|
|
716
|
-
|
|
825
|
+
// If the token is native, we need to set the value
|
|
826
|
+
const value = token.tokenAddress === 'native' ? amount : 0;
|
|
827
|
+
const tokenAddress = token.tokenAddress === 'native' ? ethers.ZeroAddress : token.tokenAddress;
|
|
717
828
|
|
|
829
|
+
// Submit the transaction
|
|
718
830
|
const tx = await paymentContract.payment(
|
|
719
831
|
tradeId,
|
|
720
|
-
tokenAddress
|
|
832
|
+
tokenAddress,
|
|
721
833
|
toAddress,
|
|
722
834
|
amount,
|
|
723
|
-
|
|
835
|
+
protocolFeeAmount,
|
|
724
836
|
deadline,
|
|
725
|
-
{
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
)
|
|
837
|
+
{ value }
|
|
838
|
+
);
|
|
729
839
|
|
|
730
|
-
|
|
840
|
+
console.log(`Transfer transaction sent: ${tx.hash}`);
|
|
731
841
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (!rpcUrl) {
|
|
739
|
-
throw new Error(`Unsupported networkId: ${networkId}`)
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const provider = new ethers.JsonRpcProvider(rpcUrl)
|
|
743
|
-
return new ethers.Wallet(this.pmmPrivateKey, provider)
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
private getPaymentAddress(networkId: string) {
|
|
747
|
-
const paymentAddress = config.getPaymentAddress(networkId)
|
|
748
|
-
if (!paymentAddress) {
|
|
749
|
-
throw new Error(`Unsupported networkId: ${networkId}`)
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
return paymentAddress
|
|
842
|
+
// Return the transaction hash with the 0x prefix
|
|
843
|
+
return `0x${tx.hash.replace(/^0x/, '')}`;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
console.error('EVM payment error:', error);
|
|
846
|
+
throw error;
|
|
753
847
|
}
|
|
754
848
|
}
|
|
755
849
|
```
|
|
@@ -760,177 +854,124 @@ In case the target chain is Bitcoin, the transaction should have at least N + 1
|
|
|
760
854
|
|
|
761
855
|
Example implementation:
|
|
762
856
|
|
|
763
|
-
```
|
|
857
|
+
```js
|
|
764
858
|
import * as bitcoin from 'bitcoinjs-lib'
|
|
765
859
|
import { ECPairFactory } from 'ecpair'
|
|
766
860
|
import * as ecc from 'tiny-secp256k1'
|
|
861
|
+
import axios from 'axios'
|
|
862
|
+
import { getTradeIdsHash } from '@optimex-xyz/market-maker-sdk'
|
|
767
863
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
interface UTXO {
|
|
773
|
-
txid: string
|
|
774
|
-
vout: number
|
|
775
|
-
value: number
|
|
776
|
-
status: {
|
|
777
|
-
confirmed: boolean
|
|
778
|
-
block_height: number
|
|
779
|
-
block_hash: string
|
|
780
|
-
block_time: number
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
@Injectable()
|
|
785
|
-
export class BTCTransferStrategy implements ITransferStrategy {
|
|
786
|
-
private readonly privateKey: string
|
|
787
|
-
private readonly ECPair = ECPairFactory(ecc)
|
|
788
|
-
|
|
789
|
-
private readonly networkMap = new Map<string, bitcoin.Network>([
|
|
790
|
-
['bitcoin_testnet', bitcoin.networks.testnet],
|
|
791
|
-
['bitcoin', bitcoin.networks.bitcoin],
|
|
792
|
-
])
|
|
793
|
-
|
|
794
|
-
private readonly rpcMap = new Map<string, string>([
|
|
795
|
-
['bitcoin_testnet', 'https://blockstream.info/testnet'],
|
|
796
|
-
['bitcoin', 'https://blockstream.info'],
|
|
797
|
-
])
|
|
864
|
+
async function makeBitcoinPayment(params) {
|
|
865
|
+
const { toAddress, amount, token, tradeId } = params
|
|
866
|
+
const ECPair = ECPairFactory(ecc)
|
|
798
867
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
bitcoin.initEccLib(ecc)
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
async transfer(params: TransferParams): Promise<string> {
|
|
805
|
-
const { toAddress, amount, token, tradeId } = params
|
|
868
|
+
// Set up Bitcoin library
|
|
869
|
+
bitcoin.initEccLib(ecc)
|
|
806
870
|
|
|
807
|
-
|
|
808
|
-
|
|
871
|
+
// Get network configuration
|
|
872
|
+
const network = getNetwork(token.networkId)
|
|
873
|
+
const rpcUrl = getRpcUrl(token.networkId)
|
|
809
874
|
|
|
810
|
-
|
|
875
|
+
// Create keypair from private key
|
|
876
|
+
const keyPair = ECPair.fromWIF(process.env.PMM_BTC_PRIVATE_KEY, network)
|
|
877
|
+
const payment = bitcoin.payments.p2tr({
|
|
878
|
+
internalPubkey: Buffer.from(keyPair.publicKey.slice(1, 33)),
|
|
879
|
+
network,
|
|
880
|
+
})
|
|
811
881
|
|
|
812
|
-
|
|
882
|
+
if (!payment.address) {
|
|
883
|
+
throw new Error('Could not generate address')
|
|
813
884
|
}
|
|
814
885
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
})
|
|
820
|
-
|
|
821
|
-
return {
|
|
822
|
-
payment: p2tr,
|
|
823
|
-
keypair: this.ECPair.fromWIF(this.privateKey, network),
|
|
824
|
-
}
|
|
886
|
+
// Get UTXOs for the address
|
|
887
|
+
const utxos = await getUTXOs(payment.address, rpcUrl)
|
|
888
|
+
if (utxos.length === 0) {
|
|
889
|
+
throw new Error(`No UTXOs found in ${token.networkSymbol} wallet`)
|
|
825
890
|
}
|
|
826
891
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
amountInSatoshis: bigint,
|
|
831
|
-
network: bitcoin.Network,
|
|
832
|
-
rpcUrl: string,
|
|
833
|
-
token: Token,
|
|
834
|
-
tradeIds: string[]
|
|
835
|
-
): Promise<string> {
|
|
836
|
-
const keyPair = this.ECPair.fromWIF(privateKey, network)
|
|
837
|
-
const { payment, keypair } = this.createPayment(keyPair.publicKey, network)
|
|
838
|
-
|
|
839
|
-
if (!payment.address) {
|
|
840
|
-
throw new Error('Could not generate address')
|
|
841
|
-
}
|
|
892
|
+
// Create and sign transaction
|
|
893
|
+
const psbt = new bitcoin.Psbt({ network })
|
|
894
|
+
let totalInput = 0n
|
|
842
895
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
896
|
+
// Add inputs
|
|
897
|
+
for (const utxo of utxos) {
|
|
898
|
+
if (!payment.output) {
|
|
899
|
+
throw new Error('Could not generate output script')
|
|
846
900
|
}
|
|
847
901
|
|
|
848
|
-
const
|
|
849
|
-
let totalInput = 0n
|
|
850
|
-
|
|
851
|
-
for (const utxo of utxos) {
|
|
852
|
-
if (!payment.output) {
|
|
853
|
-
throw new Error('Could not generate output script')
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
const internalKey = Buffer.from(keypair.publicKey.slice(1, 33))
|
|
857
|
-
|
|
858
|
-
psbt.addInput({
|
|
859
|
-
hash: utxo.txid,
|
|
860
|
-
index: utxo.vout,
|
|
861
|
-
witnessUtxo: {
|
|
862
|
-
script: payment.output,
|
|
863
|
-
value: BigInt(utxo.value),
|
|
864
|
-
},
|
|
865
|
-
tapInternalKey: internalKey,
|
|
866
|
-
})
|
|
867
|
-
|
|
868
|
-
totalInput += BigInt(utxo.value)
|
|
869
|
-
}
|
|
902
|
+
const internalKey = Buffer.from(keyPair.publicKey.slice(1, 33))
|
|
870
903
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
904
|
+
psbt.addInput({
|
|
905
|
+
hash: utxo.txid,
|
|
906
|
+
index: utxo.vout,
|
|
907
|
+
witnessUtxo: {
|
|
908
|
+
script: payment.output,
|
|
909
|
+
value: BigInt(utxo.value),
|
|
910
|
+
},
|
|
911
|
+
tapInternalKey: internalKey,
|
|
912
|
+
})
|
|
879
913
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const changeAmount = totalInput - amountInSatoshis - fee
|
|
914
|
+
totalInput += BigInt(utxo.value)
|
|
915
|
+
}
|
|
883
916
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
917
|
+
// Check if we have enough balance
|
|
918
|
+
if (totalInput < amount) {
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Insufficient balance. Need ${amount} satoshis, but only have ${totalInput} satoshis`
|
|
921
|
+
)
|
|
922
|
+
}
|
|
888
923
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
})
|
|
894
|
-
}
|
|
924
|
+
// Get fee rate
|
|
925
|
+
const feeRate = await getFeeRate(rpcUrl)
|
|
926
|
+
const fee = BigInt(Math.ceil(200 * feeRate))
|
|
927
|
+
const changeAmount = totalInput - amount - fee
|
|
895
928
|
|
|
896
|
-
|
|
929
|
+
// Add recipient output
|
|
930
|
+
psbt.addOutput({
|
|
931
|
+
address: toAddress,
|
|
932
|
+
value: amount,
|
|
933
|
+
})
|
|
897
934
|
|
|
898
|
-
|
|
935
|
+
// Add change output if needed
|
|
936
|
+
if (changeAmount > 546n) {
|
|
899
937
|
psbt.addOutput({
|
|
900
|
-
|
|
901
|
-
value:
|
|
938
|
+
address: payment.address,
|
|
939
|
+
value: changeAmount,
|
|
902
940
|
})
|
|
941
|
+
}
|
|
903
942
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
943
|
+
// Add OP_RETURN output with trade ID hash
|
|
944
|
+
const tradeIdsHash = getTradeIdsHash([tradeId])
|
|
945
|
+
psbt.addOutput({
|
|
946
|
+
script: bitcoin.script.compile([
|
|
947
|
+
bitcoin.opcodes.OP_RETURN,
|
|
948
|
+
Buffer.from(tradeIdsHash.slice(2), 'hex')
|
|
949
|
+
]),
|
|
950
|
+
value: 0n,
|
|
951
|
+
})
|
|
952
|
+
|
|
953
|
+
// Sign inputs
|
|
954
|
+
const toXOnly = (pubKey) => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33))
|
|
955
|
+
const tweakedSigner = keyPair.tweak(bitcoin.crypto.taggedHash('TapTweak', toXOnly(keyPair.publicKey)))
|
|
956
|
+
|
|
957
|
+
for (let i = 0; i < psbt.data.inputs.length; i++) {
|
|
958
|
+
psbt.signInput(i, tweakedSigner, [bitcoin.Transaction.SIGHASH_DEFAULT])
|
|
959
|
+
}
|
|
913
960
|
|
|
914
|
-
|
|
915
|
-
const rawTx = tx.toHex()
|
|
961
|
+
psbt.finalizeAllInputs()
|
|
916
962
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
},
|
|
921
|
-
})
|
|
963
|
+
// Extract transaction
|
|
964
|
+
const tx = psbt.extractTransaction()
|
|
965
|
+
const rawTx = tx.toHex()
|
|
922
966
|
|
|
923
|
-
|
|
924
|
-
}
|
|
967
|
+
// Broadcast transaction
|
|
968
|
+
const response = await axios.post(`${rpcUrl}/api/tx`, rawTx, {
|
|
969
|
+
headers: {
|
|
970
|
+
'Content-Type': 'text/plain',
|
|
971
|
+
},
|
|
972
|
+
})
|
|
925
973
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
return response.data
|
|
929
|
-
}
|
|
974
|
+
return response.data // Transaction ID
|
|
975
|
+
}
|
|
930
976
|
|
|
931
|
-
|
|
932
|
-
try {
|
|
933
|
-
const response = await axios.get<{ [key: string]: number }>(`${rpcUrl}/api/fee-estimates`)
|
|
934
|
-
return response.data[0]
|
|
935
|
-
} catch (error) {
|
|
936
|
-
console.error(`Error fetching
|
|
977
|
+
```
|