@optimex-xyz/market-maker-sdk 0.9.0-staging-adbbc25 → 0.9.1

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 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
- - [1. Overview](#1-overview)
20
- - [2. Quick Start](#2-quick-start)
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
- - [3.2. Endpoint: `/commitment-quote`](#32-endpoint-commitment-quote)
24
- - [3.3. Endpoint: `/settlement-signature`](#33-endpoint-settlement-signature)
25
- - [3.4. Endpoint: `/ack-settlement`](#34-endpoint-ack-settlement)
26
- - [3.5. Endpoint: `/signal-payment`](#35-endpoint-signal-payment)
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 | Description |
81
- | ---------------- | -------------------------------------------------------------------- |
82
- | `dev` | internal environment with test networks and development services |
83
- | `staging` | Staging environment with test networks and staging services |
84
- | `prelive` | Pre production environment with mainnet networks for testing before release |
85
- | `production` | Production environment with mainnet networks and production services |
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
- // Find the from token and to token
190
- const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
191
- const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
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 || !toToken) {
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: 'Token not found'
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
- // Note: Treat amount as BigInt
204
- const amountBigInt = BigInt(amount);
205
- const quote = calculateQuote(fromToken, toToken, amountBigInt);
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
- // Get the receiving address for this token pair
208
- const pmmReceivingAddress = getPMMReceivingAddress(fromToken.network_id);
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 = await sessionRepository.findById(session_id);
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 not found'
300
- });
416
+ error: 'Session expired during processing',
417
+ })
301
418
  }
302
419
 
303
- // Fetch token information from Solver API
304
- const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
305
- const tokensData = await tokensResponse.json();
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
- // Find the from token and to token
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: 'Token not found'
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
- // Note: Treat numeric values as BigInt
321
- const amountBigInt = BigInt(amount);
322
- const quote = calculateFinalQuote(fromToken, toToken, amountBigInt, trade_deadline);
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
- amount: amountBigInt.toString(),
331
- fromUserAddress: from_user_address,
332
- toUserAddress: to_user_address,
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
- commitmentQuote: quote.toString()
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: `/settlement-signature`
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 details from Solver API
416
- const tradeDetailsResponse = await fetch(`https://api.solver.example/v1/market-maker/trades/${trade_id}`);
417
- const tradeDetails = await tradeDetailsResponse.json();
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 data
423
- const pmmId = process.env.PMM_ID; // Your 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
- // Get the presigns and trade data from tradeDetails
426
- const { from_token, to_token } = tradeDetails.data;
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
- // Create a commitment info hash
429
- // Note: Treat numeric values as BigInt
430
- const committedQuoteBigInt = BigInt(committed_quote);
431
- const commitInfoHash = createCommitInfoHash(
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
- trade.pmmReceivingAddress,
434
- to_token.chain,
435
- to_token.address,
436
- committedQuoteBigInt,
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
- // Sign the commitment with your private key
441
- const privateKey = process.env.PMM_PRIVATE_KEY;
442
- const signature = signMessage(privateKey, trade_id, commitInfoHash);
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.4. Endpoint: `/ack-settlement`
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 it was chosen
522
- await tradeRepository.update(trade_id, {
523
- chosen: chosen === 'true',
524
- tradeDeadline: trade_deadline,
525
- scriptDeadline: script_deadline
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.5. Endpoint: `/signal-payment`
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
- // Update trade with fee amount
604
- await tradeRepository.update(trade_id, {
605
- totalFeeAmount: total_fee_amount,
606
- tradeDeadline: trade_deadline,
607
- scriptDeadline: script_deadline
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
- totalFeeAmount: total_fee_amount
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
- "data": {
665
- "supported_networks": [
666
- {
667
- "network_id": "bitcoin_testnet",
668
- "name": "Bitcoin Testnet",
669
- "symbol": "tBTC",
670
- "type": "BTC",
671
- "logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg"
672
- },
673
- {
674
- "network_id": "ethereum_sepolia",
675
- "name": "Ethereum Sepolia",
676
- "symbol": "ETH",
677
- "type": "EVM",
678
- "logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg"
679
- }
680
- ],
681
- "tokens": [
682
- {
683
- "id": 2,
684
- "network_id": "bitcoin_testnet",
685
- "token_id": "tBTC",
686
- "network_name": "Bitcoin Testnet",
687
- "network_symbol": "tBTC",
688
- "network_type": "BTC",
689
- "token_name": "Bitcoin Testnet",
690
- "token_symbol": "tBTC",
691
- "token_address": "native",
692
- "token_decimals": 8,
693
- "token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/tbtc.svg",
694
- "network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg",
695
- "active": true,
696
- "created_at": "2024-10-28T07:24:33.179Z",
697
- "updated_at": "2024-11-07T04:40:46.454Z"
698
- },
699
- {
700
- "id": 11,
701
- "network_id": "ethereum_sepolia",
702
- "token_id": "ETH",
703
- "network_name": "Ethereum Sepolia",
704
- "network_symbol": "ETH",
705
- "network_type": "EVM",
706
- "token_name": "Ethereum Sepolia",
707
- "token_symbol": "ETH",
708
- "token_address": "native",
709
- "token_decimals": 18,
710
- "token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth.svg",
711
- "network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg",
712
- "active": true,
713
- "created_at": "2024-11-22T08:36:59.175Z",
714
- "updated_at": "2024-11-22T08:36:59.175Z"
715
- }
716
- ],
717
- "pairs": [
718
- {
719
- "from_token_id": "ETH",
720
- "to_token_id": "tBTC",
721
- "is_active": true
722
- },
723
- {
724
- "from_token_id": "tBTC",
725
- "to_token_id": "ETH",
726
- "is_active": true
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
- - **For EVM Chains:**
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
- - **For Bitcoin or Solana:**
767
- - Must encode raw_tx string using the `l2Encode` function
768
- - Example raw_tx string: `3d83c7846d6e5b04279175a9592705a15373f3029b866d5224cc0744489fe403`
769
- - After encoding
770
- ```
771
- "settlement_tx": "0x33643833633738343664366535623034323739313735613935393237303561313533373366333032396238363664353232346363303734343438396665343033"
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
- "function payment(bytes32 tradeId, address token, address recipient, uint256 amount, uint256 feeAmount, uint256 deadline) payable returns (bool)"
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
- tradeId,
968
- tokenAddress,
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
  ```