@optimex-xyz/market-maker-sdk 0.4.6 → 0.4.8
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 +435 -520
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +1 -1
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
|
| ------------ | -------------------------------------------------------------------- |
|
|
@@ -179,34 +158,49 @@ GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=10000000000000000
|
|
|
179
158
|
#### Example Implementation
|
|
180
159
|
|
|
181
160
|
```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
|
-
|
|
161
|
+
async function getIndicativeQuote(req, res) {
|
|
162
|
+
try {
|
|
163
|
+
const { from_token_id, to_token_id, amount, session_id } = req.query;
|
|
164
|
+
|
|
165
|
+
// Generate a session ID if not provided
|
|
166
|
+
const sessionId = session_id || generateSessionId();
|
|
167
|
+
|
|
168
|
+
// Fetch token information from Solver API
|
|
169
|
+
const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
|
|
170
|
+
const tokensData = await tokensResponse.json();
|
|
171
|
+
|
|
172
|
+
// Find the from token and to token
|
|
173
|
+
const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
|
|
174
|
+
const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
|
|
175
|
+
|
|
176
|
+
if (!fromToken || !toToken) {
|
|
177
|
+
return res.status(400).json({
|
|
178
|
+
session_id: sessionId,
|
|
179
|
+
pmm_receiving_address: '',
|
|
180
|
+
indicative_quote: '0',
|
|
181
|
+
error: 'Token not found'
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Calculate the quote (implementation specific to your PMM)
|
|
186
|
+
const quote = calculateQuote(fromToken, toToken, amount);
|
|
187
|
+
|
|
188
|
+
// Get the receiving address for this token pair
|
|
189
|
+
const pmmReceivingAddress = getPMMReceivingAddress(fromToken.network_id);
|
|
190
|
+
|
|
191
|
+
return res.status(200).json({
|
|
192
|
+
session_id: sessionId,
|
|
193
|
+
pmm_receiving_address: pmmReceivingAddress,
|
|
194
|
+
indicative_quote: quote,
|
|
195
|
+
error: ''
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return res.status(500).json({
|
|
199
|
+
session_id: req.query.session_id || '',
|
|
200
|
+
pmm_receiving_address: '',
|
|
201
|
+
indicative_quote: '0',
|
|
202
|
+
error: error.message
|
|
203
|
+
});
|
|
210
204
|
}
|
|
211
205
|
}
|
|
212
206
|
```
|
|
@@ -259,44 +253,78 @@ GET /commitment-quote?session_id=12345&trade_id=abcd1234&from_token_id=ETH&to_to
|
|
|
259
253
|
#### Example Implementation
|
|
260
254
|
|
|
261
255
|
```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
|
-
|
|
256
|
+
async function getCommitmentQuote(req, res) {
|
|
257
|
+
try {
|
|
258
|
+
const {
|
|
259
|
+
session_id,
|
|
260
|
+
trade_id,
|
|
261
|
+
from_token_id,
|
|
262
|
+
to_token_id,
|
|
263
|
+
amount,
|
|
264
|
+
from_user_address,
|
|
265
|
+
to_user_address,
|
|
266
|
+
user_deposit_tx,
|
|
267
|
+
user_deposit_vault,
|
|
268
|
+
trade_deadline,
|
|
269
|
+
script_deadline
|
|
270
|
+
} = req.query;
|
|
271
|
+
|
|
272
|
+
// Validate the session exists
|
|
273
|
+
const session = await sessionRepository.findById(session_id);
|
|
274
|
+
if (!session) {
|
|
275
|
+
return res.status(400).json({
|
|
276
|
+
trade_id,
|
|
277
|
+
commitment_quote: '0',
|
|
278
|
+
error: 'Session not found'
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Fetch token information from Solver API
|
|
283
|
+
const tokensResponse = await fetch('https://api.solver.example/v1/market-maker/tokens');
|
|
284
|
+
const tokensData = await tokensResponse.json();
|
|
285
|
+
|
|
286
|
+
// Find the from token and to token
|
|
287
|
+
const fromToken = tokensData.data.tokens.find(token => token.token_id === from_token_id);
|
|
288
|
+
const toToken = tokensData.data.tokens.find(token => token.token_id === to_token_id);
|
|
289
|
+
|
|
290
|
+
if (!fromToken || !toToken) {
|
|
291
|
+
return res.status(400).json({
|
|
292
|
+
trade_id,
|
|
293
|
+
commitment_quote: '0',
|
|
294
|
+
error: 'Token not found'
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Calculate the final quote (implementation specific to your PMM)
|
|
299
|
+
const quote = calculateFinalQuote(fromToken, toToken, amount, trade_deadline);
|
|
300
|
+
|
|
301
|
+
// Store the trade in the database
|
|
302
|
+
await tradeRepository.create({
|
|
303
|
+
tradeId: trade_id,
|
|
304
|
+
sessionId: session_id,
|
|
305
|
+
fromTokenId: from_token_id,
|
|
306
|
+
toTokenId: to_token_id,
|
|
307
|
+
amount,
|
|
308
|
+
fromUserAddress: from_user_address,
|
|
309
|
+
toUserAddress: to_user_address,
|
|
310
|
+
userDepositTx: user_deposit_tx,
|
|
311
|
+
userDepositVault: user_deposit_vault,
|
|
312
|
+
tradeDeadline: trade_deadline,
|
|
313
|
+
scriptDeadline: script_deadline,
|
|
314
|
+
commitmentQuote: quote
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return res.status(200).json({
|
|
318
|
+
trade_id,
|
|
319
|
+
commitment_quote: quote,
|
|
320
|
+
error: ''
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
return res.status(500).json({
|
|
324
|
+
trade_id: req.query.trade_id || '',
|
|
325
|
+
commitment_quote: '0',
|
|
326
|
+
error: error.message
|
|
327
|
+
});
|
|
300
328
|
}
|
|
301
329
|
}
|
|
302
330
|
```
|
|
@@ -343,77 +371,62 @@ GET /settlement-signature?trade_id=abcd1234&committed_quote=987654321000000000&t
|
|
|
343
371
|
|
|
344
372
|
#### Example Implementation
|
|
345
373
|
|
|
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> {
|
|
374
|
+
```js
|
|
375
|
+
async function getSettlementSignature(req, res) {
|
|
365
376
|
try {
|
|
366
|
-
const {
|
|
377
|
+
const { trade_id, committed_quote, trade_deadline, script_deadline } = req.query;
|
|
378
|
+
|
|
379
|
+
// Fetch the trade from the database
|
|
380
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
381
|
+
if (!trade) {
|
|
382
|
+
return res.status(400).json({
|
|
383
|
+
trade_id,
|
|
384
|
+
signature: '',
|
|
385
|
+
deadline: 0,
|
|
386
|
+
error: 'Trade not found'
|
|
387
|
+
});
|
|
388
|
+
}
|
|
367
389
|
|
|
368
|
-
//
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
routerService.getTradeData(tradeId),
|
|
372
|
-
])
|
|
390
|
+
// Fetch trade details from Solver API
|
|
391
|
+
const tradeDetailsResponse = await fetch(`https://api.solver.example/v1/market-maker/trades/${trade_id}`);
|
|
392
|
+
const tradeDetails = await tradeDetailsResponse.json();
|
|
373
393
|
|
|
374
|
-
|
|
375
|
-
const deadline =
|
|
394
|
+
// Calculate a deadline (30 minutes from now)
|
|
395
|
+
const deadline = Math.floor(Date.now() / 1000) + 1800;
|
|
376
396
|
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
if (!pmmPresign) {
|
|
380
|
-
throw new BadRequestException('pmmPresign not found')
|
|
381
|
-
}
|
|
397
|
+
// Get PMM data
|
|
398
|
+
const pmmId = process.env.PMM_ID; // Your PMM ID
|
|
382
399
|
|
|
383
|
-
//
|
|
384
|
-
const
|
|
400
|
+
// Get the presigns and trade data from tradeDetails
|
|
401
|
+
const { from_token, to_token } = tradeDetails.data;
|
|
385
402
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
403
|
+
// Create a commitment info hash
|
|
404
|
+
const commitInfoHash = createCommitInfoHash(
|
|
405
|
+
pmmId,
|
|
406
|
+
trade.pmmReceivingAddress,
|
|
407
|
+
to_token.chain,
|
|
408
|
+
to_token.address,
|
|
409
|
+
committed_quote,
|
|
392
410
|
deadline
|
|
393
|
-
)
|
|
411
|
+
);
|
|
394
412
|
|
|
395
|
-
|
|
413
|
+
// Sign the commitment with your private key
|
|
414
|
+
const privateKey = process.env.PMM_PRIVATE_KEY;
|
|
415
|
+
const signature = signMessage(privateKey, trade_id, commitInfoHash);
|
|
396
416
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const signature = await getSignature(
|
|
400
|
-
this.pmmWallet,
|
|
401
|
-
this.provider,
|
|
402
|
-
signerAddress,
|
|
403
|
-
tradeId,
|
|
404
|
-
commitInfoHash,
|
|
405
|
-
SignatureType.VerifyingContract,
|
|
406
|
-
domain
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
tradeId: tradeId,
|
|
417
|
+
return res.status(200).json({
|
|
418
|
+
trade_id,
|
|
411
419
|
signature,
|
|
412
|
-
deadline
|
|
413
|
-
error: ''
|
|
414
|
-
}
|
|
415
|
-
} catch (error
|
|
416
|
-
|
|
420
|
+
deadline,
|
|
421
|
+
error: ''
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
return res.status(500).json({
|
|
425
|
+
trade_id: req.query.trade_id || '',
|
|
426
|
+
signature: '',
|
|
427
|
+
deadline: 0,
|
|
428
|
+
error: error.message
|
|
429
|
+
});
|
|
417
430
|
}
|
|
418
431
|
}
|
|
419
432
|
```
|
|
@@ -461,30 +474,39 @@ trade_id=abcd1234&trade_deadline=1696012800&script_deadline=1696016400&chosen=tr
|
|
|
461
474
|
|
|
462
475
|
#### Example Implementation
|
|
463
476
|
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
477
|
+
```js
|
|
478
|
+
async function ackSettlement(req, res) {
|
|
479
|
+
try {
|
|
480
|
+
const { trade_id, trade_deadline, script_deadline, chosen } = req.body;
|
|
481
|
+
|
|
482
|
+
// Fetch the trade from the database
|
|
483
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
484
|
+
if (!trade) {
|
|
485
|
+
return res.status(400).json({
|
|
486
|
+
trade_id,
|
|
487
|
+
status: 'error',
|
|
488
|
+
error: 'Trade not found'
|
|
489
|
+
});
|
|
490
|
+
}
|
|
473
491
|
|
|
474
|
-
|
|
492
|
+
// Update trade status based on whether it was chosen
|
|
493
|
+
await tradeRepository.update(trade_id, {
|
|
494
|
+
chosen: chosen === 'true',
|
|
495
|
+
tradeDeadline: trade_deadline,
|
|
496
|
+
scriptDeadline: script_deadline
|
|
497
|
+
});
|
|
475
498
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
tradeId: dto.tradeId,
|
|
499
|
+
return res.status(200).json({
|
|
500
|
+
trade_id,
|
|
480
501
|
status: 'acknowledged',
|
|
481
|
-
error: ''
|
|
482
|
-
}
|
|
483
|
-
} catch (error
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
502
|
+
error: ''
|
|
503
|
+
});
|
|
504
|
+
} catch (error) {
|
|
505
|
+
return res.status(500).json({
|
|
506
|
+
trade_id: req.body.trade_id || '',
|
|
507
|
+
status: 'error',
|
|
508
|
+
error: error.message
|
|
509
|
+
});
|
|
488
510
|
}
|
|
489
511
|
}
|
|
490
512
|
```
|
|
@@ -500,7 +522,7 @@ Used by the solver to signal the chosen PMM to start submitting their payment.
|
|
|
500
522
|
- **HTTP Method**: `POST`
|
|
501
523
|
- **Form Parameters**:
|
|
502
524
|
- `trade_id` (string): The unique identifier for the trade.
|
|
503
|
-
- `
|
|
525
|
+
- `total_fee_amount` (string): The amount of total fee the PMM has to submit, in base 10.
|
|
504
526
|
- `trade_deadline` (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
|
|
505
527
|
- `script_deadline` (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
|
|
506
528
|
|
|
@@ -510,7 +532,7 @@ Used by the solver to signal the chosen PMM to start submitting their payment.
|
|
|
510
532
|
POST /signal-payment
|
|
511
533
|
Content-Type: application/x-www-form-urlencoded
|
|
512
534
|
|
|
513
|
-
trade_id=abcd1234&
|
|
535
|
+
trade_id=abcd1234&total_fee_amount=1000000000000000&trade_deadline=1696012800&script_deadline=1696016400
|
|
514
536
|
```
|
|
515
537
|
|
|
516
538
|
#### Expected Response
|
|
@@ -532,121 +554,202 @@ trade_id=abcd1234&protocol_fee_amount=1000000000000000&trade_deadline=1696012800
|
|
|
532
554
|
|
|
533
555
|
#### Example Implementation
|
|
534
556
|
|
|
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> {
|
|
557
|
+
```js
|
|
558
|
+
async function signalPayment(req, res) {
|
|
546
559
|
try {
|
|
547
|
-
|
|
560
|
+
const { trade_id, total_fee_amount, trade_deadline, script_deadline } = req.body;
|
|
561
|
+
|
|
562
|
+
// Fetch the trade from the database
|
|
563
|
+
const trade = await tradeRepository.findById(trade_id);
|
|
564
|
+
if (!trade) {
|
|
565
|
+
return res.status(400).json({
|
|
566
|
+
trade_id,
|
|
567
|
+
status: 'error',
|
|
568
|
+
error: 'Trade not found'
|
|
569
|
+
});
|
|
570
|
+
}
|
|
548
571
|
|
|
549
|
-
|
|
550
|
-
|
|
572
|
+
// Update trade with fee amount
|
|
573
|
+
await tradeRepository.update(trade_id, {
|
|
574
|
+
totalFeeAmount: total_fee_amount,
|
|
575
|
+
tradeDeadline: trade_deadline,
|
|
576
|
+
scriptDeadline: script_deadline
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Queue the payment task
|
|
580
|
+
await paymentQueue.add({
|
|
581
|
+
tradeId: trade_id,
|
|
582
|
+
totalFeeAmount: total_fee_amount
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
return res.status(200).json({
|
|
586
|
+
trade_id,
|
|
551
587
|
status: 'acknowledged',
|
|
552
|
-
error: ''
|
|
553
|
-
}
|
|
554
|
-
} catch (error
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
588
|
+
error: ''
|
|
589
|
+
});
|
|
590
|
+
} catch (error) {
|
|
591
|
+
return res.status(500).json({
|
|
592
|
+
trade_id: req.body.trade_id || '',
|
|
593
|
+
status: 'error',
|
|
594
|
+
error: error.message
|
|
595
|
+
});
|
|
559
596
|
}
|
|
560
597
|
}
|
|
561
598
|
```
|
|
562
599
|
|
|
563
|
-
## 4.
|
|
600
|
+
## 4. Solver API Endpoints for PMMs
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
These API endpoints are provided by the Solver backend for PMMs to retrieve token information and submit settlement data.
|
|
564
604
|
|
|
565
|
-
|
|
605
|
+
> **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.
|
|
566
606
|
|
|
567
|
-
|
|
607
|
+
|
|
608
|
+
### 4.1. Endpoint: `/v1/market-maker/tokens`
|
|
568
609
|
|
|
569
610
|
#### Description
|
|
570
611
|
|
|
571
|
-
Returns a list of
|
|
612
|
+
Returns a list of tokens supported by the Solver Backend.
|
|
613
|
+
|
|
614
|
+
#### Request Parameters
|
|
572
615
|
|
|
573
|
-
|
|
616
|
+
- **HTTP Method**: `GET`
|
|
574
617
|
|
|
575
|
-
|
|
576
|
-
import { tokenService } from '@optimex-xyz/market-maker-sdk'
|
|
618
|
+
#### Example Request
|
|
577
619
|
|
|
578
|
-
|
|
620
|
+
```
|
|
621
|
+
GET /v1/market-maker/tokens
|
|
579
622
|
```
|
|
580
623
|
|
|
581
|
-
|
|
624
|
+
#### Expected Response
|
|
625
|
+
|
|
626
|
+
- **HTTP Status**: `200 OK`
|
|
627
|
+
- **Response Body** (JSON):
|
|
628
|
+
|
|
629
|
+
```json
|
|
630
|
+
{
|
|
631
|
+
"data": {
|
|
632
|
+
"supported_networks": [
|
|
633
|
+
{
|
|
634
|
+
"network_id": "bitcoin_testnet",
|
|
635
|
+
"name": "Bitcoin Testnet",
|
|
636
|
+
"symbol": "tBTC",
|
|
637
|
+
"type": "BTC",
|
|
638
|
+
"logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/btc_network.svg"
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
"network_id": "ethereum_sepolia",
|
|
642
|
+
"name": "Ethereum Sepolia",
|
|
643
|
+
"symbol": "ETH",
|
|
644
|
+
"type": "EVM",
|
|
645
|
+
"logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth_network.svg"
|
|
646
|
+
}
|
|
647
|
+
],
|
|
648
|
+
"tokens": [
|
|
649
|
+
{
|
|
650
|
+
"id": 2,
|
|
651
|
+
"network_id": "bitcoin_testnet",
|
|
652
|
+
"token_id": "tBTC",
|
|
653
|
+
"network_name": "Bitcoin Testnet",
|
|
654
|
+
"network_symbol": "tBTC",
|
|
655
|
+
"network_type": "BTC",
|
|
656
|
+
"token_name": "Bitcoin Testnet",
|
|
657
|
+
"token_symbol": "tBTC",
|
|
658
|
+
"token_address": "native",
|
|
659
|
+
"token_decimals": 8,
|
|
660
|
+
"token_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/tbtc.svg",
|
|
661
|
+
"network_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/btc_network.svg",
|
|
662
|
+
"active": true,
|
|
663
|
+
"created_at": "2024-10-28T07:24:33.179Z",
|
|
664
|
+
"updated_at": "2024-11-07T04:40:46.454Z"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
"id": 11,
|
|
668
|
+
"network_id": "ethereum_sepolia",
|
|
669
|
+
"token_id": "ETH",
|
|
670
|
+
"network_name": "Ethereum Sepolia",
|
|
671
|
+
"network_symbol": "ETH",
|
|
672
|
+
"network_type": "EVM",
|
|
673
|
+
"token_name": "Ethereum Sepolia",
|
|
674
|
+
"token_symbol": "ETH",
|
|
675
|
+
"token_address": "native",
|
|
676
|
+
"token_decimals": 18,
|
|
677
|
+
"token_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth.svg",
|
|
678
|
+
"network_logo_uri": "https://storage.googleapis.com/bitfi-static-35291d79/images/tokens/eth_network.svg",
|
|
679
|
+
"active": true,
|
|
680
|
+
"created_at": "2024-11-22T08:36:59.175Z",
|
|
681
|
+
"updated_at": "2024-11-22T08:36:59.175Z"
|
|
682
|
+
}
|
|
683
|
+
],
|
|
684
|
+
"pairs": [
|
|
685
|
+
{
|
|
686
|
+
"from_token_id": "ETH",
|
|
687
|
+
"to_token_id": "tBTC",
|
|
688
|
+
"is_active": true
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"from_token_id": "tBTC",
|
|
692
|
+
"to_token_id": "ETH",
|
|
693
|
+
"is_active": true
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### 4.2. Endpoint: `/v1/market-maker/submit-settlement-tx`
|
|
582
701
|
|
|
583
702
|
#### Description
|
|
584
703
|
|
|
585
704
|
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
705
|
|
|
587
|
-
Parameters
|
|
706
|
+
#### Request Parameters
|
|
707
|
+
|
|
708
|
+
- **HTTP Method**: `POST`
|
|
709
|
+
- **Request Body** (JSON):
|
|
710
|
+
|
|
711
|
+
```json
|
|
712
|
+
{
|
|
713
|
+
"trade_ids": ["0xTradeID1", "0xTradeID2", "..."],
|
|
714
|
+
"pmm_id": "pmm001",
|
|
715
|
+
"settlement_tx": "SettlementTransactionData",
|
|
716
|
+
"signature": "0xSignatureData",
|
|
717
|
+
"start_index": 0,
|
|
718
|
+
"signed_at": 1719158400
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
588
722
|
- `trade_ids` (array of strings): An array of trade IDs associated with the settlement transaction.
|
|
589
723
|
- `pmm_id` (string): The PMM's ID, which must match the one committed for the trade(s).
|
|
590
|
-
- `settlement_tx` (string): The raw transaction data
|
|
724
|
+
- `settlement_tx` (string): The raw transaction data representing the settlement.
|
|
591
725
|
- `signature` (string): The PMM's signature on the settlement transaction.
|
|
592
726
|
- `start_index` (integer): The index indicating the starting point for settlement processing (used for batch settlements).
|
|
593
727
|
- `signed_at` (integer): The UNIX timestamp (in seconds) when the PMM signed the settlement transaction.
|
|
594
728
|
|
|
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))
|
|
729
|
+
#### Example Request
|
|
619
730
|
|
|
620
|
-
|
|
731
|
+
```
|
|
732
|
+
POST /v1/market-maker/submit-settlement-tx
|
|
733
|
+
Content-Type: application/json
|
|
621
734
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const pmmId = ... // string
|
|
633
|
-
const requestPayload = {
|
|
634
|
-
tradeIds: [tradeId],
|
|
635
|
-
pmmId: pmmId,
|
|
636
|
-
settlementTx: ensureHexPrefix(paymentTxId),
|
|
637
|
-
signature: signature,
|
|
638
|
-
startIndex: 0,
|
|
639
|
-
signedAt: signedAt,
|
|
640
|
-
}
|
|
735
|
+
{
|
|
736
|
+
"trade_ids": ["0xabcdef123456...", "0x123456abcdef..."],
|
|
737
|
+
"pmm_id": "pmm001",
|
|
738
|
+
"settlement_tx": "0xRawTransactionData",
|
|
739
|
+
"signature": "0xSignatureData",
|
|
740
|
+
"start_index": 0,
|
|
741
|
+
"signed_at": 1719158400
|
|
742
|
+
}
|
|
743
|
+
```
|
|
641
744
|
|
|
642
|
-
|
|
745
|
+
#### Expected Response
|
|
643
746
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
this.logger.error('submit settlement error', error.stack)
|
|
747
|
+
- **HTTP Status**: `200 OK`
|
|
748
|
+
- **Response Body** (JSON):
|
|
647
749
|
|
|
648
|
-
|
|
649
|
-
|
|
750
|
+
```json
|
|
751
|
+
{
|
|
752
|
+
"message": "Settlement transaction submitted successfully"
|
|
650
753
|
}
|
|
651
754
|
```
|
|
652
755
|
|
|
@@ -656,100 +759,85 @@ async submit(job: Job<string>) {
|
|
|
656
759
|
- **Start Index**: Used when submitting a batch of settlements to indicate the position within the batch.
|
|
657
760
|
- **Signature**: Must be valid and verifiable by the solver backend.
|
|
658
761
|
|
|
659
|
-
|
|
762
|
+
### 4.3. Endpoint: `/v1/market-maker/trades/:tradeId`
|
|
660
763
|
|
|
661
|
-
|
|
662
|
-
import { Token } from '@optimex-xyz/market-maker-sdk'
|
|
764
|
+
#### Description
|
|
663
765
|
|
|
664
|
-
|
|
665
|
-
toAddress: string
|
|
666
|
-
amount: bigint
|
|
667
|
-
token: Token
|
|
668
|
-
tradeId: string
|
|
669
|
-
}
|
|
766
|
+
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
767
|
|
|
671
|
-
|
|
672
|
-
transfer(params: TransferParams): Promise<string>
|
|
673
|
-
}
|
|
674
|
-
```
|
|
768
|
+
#### Request Parameters
|
|
675
769
|
|
|
676
|
-
|
|
770
|
+
- **HTTP Method**: `GET`
|
|
771
|
+
- **Path Parameters**:
|
|
772
|
+
- `tradeId` (string): The unique identifier for the trade to retrieve.
|
|
677
773
|
|
|
678
|
-
|
|
774
|
+
#### Example Request
|
|
679
775
|
|
|
680
|
-
|
|
776
|
+
```
|
|
777
|
+
GET /v1/market-maker/trades/0x650e2c921a85eb0b8831ff838d4d98c0a5cd2ede5c0cb6bb4a15969f51c75424
|
|
778
|
+
```
|
|
681
779
|
|
|
682
|
-
|
|
683
|
-
import { config, ensureHexPrefix, ERC20__factory, Payment__factory, routerService } from '@optimex-xyz/market-maker-sdk'
|
|
780
|
+
#### Expected Response
|
|
684
781
|
|
|
685
|
-
|
|
782
|
+
- **HTTP Status**: `200 OK`
|
|
783
|
+
- **Response Body** (JSON): See the detailed response format in the updated PMM Integration API Documentation.
|
|
686
784
|
|
|
687
|
-
|
|
688
|
-
export class EVMTransferStrategy implements ITransferStrategy {
|
|
689
|
-
private pmmPrivateKey: string
|
|
785
|
+
## 5. PMM Making Payment
|
|
690
786
|
|
|
691
|
-
|
|
692
|
-
private readonly rpcMap = new Map<string, string>([['ethereum_sepolia', 'https://eth-sepolia.public.blastapi.io']])
|
|
787
|
+
### 5.1. EVM
|
|
693
788
|
|
|
694
|
-
|
|
695
|
-
this.pmmPrivateKey = this.configService.getOrThrow<string>('PMM_EVM_PRIVATE_KEY')
|
|
696
|
-
}
|
|
789
|
+
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
790
|
|
|
698
|
-
|
|
699
|
-
const { toAddress, amount, token, tradeId } = params
|
|
700
|
-
const { tokenAddress, networkId } = token
|
|
791
|
+
Example implementation:
|
|
701
792
|
|
|
702
|
-
|
|
793
|
+
```js
|
|
794
|
+
const { ethers } = require('ethers');
|
|
703
795
|
|
|
704
|
-
|
|
796
|
+
async function makeEVMPayment(tradeId, toAddress, amount, token, protocolFeeAmount) {
|
|
797
|
+
try {
|
|
798
|
+
// Get the private key from your secure storage
|
|
799
|
+
const privateKey = process.env.PMM_EVM_PRIVATE_KEY;
|
|
705
800
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
801
|
+
// Set up the provider and signer
|
|
802
|
+
const rpcUrl = getRpcUrlForNetwork(token.networkId);
|
|
803
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
804
|
+
const signer = new ethers.Wallet(privateKey, provider);
|
|
709
805
|
|
|
710
|
-
|
|
806
|
+
// Get the payment contract address
|
|
807
|
+
const paymentAddress = getPaymentAddressForNetwork(token.networkId);
|
|
711
808
|
|
|
712
|
-
|
|
809
|
+
// Create the contract instance
|
|
810
|
+
const paymentAbi = [
|
|
811
|
+
// ABI for the payment contract
|
|
812
|
+
"function payment(bytes32 tradeId, address token, address recipient, uint256 amount, uint256 feeAmount, uint256 deadline) payable returns (bool)"
|
|
813
|
+
];
|
|
814
|
+
const paymentContract = new ethers.Contract(paymentAddress, paymentAbi, signer);
|
|
713
815
|
|
|
714
|
-
|
|
816
|
+
// Calculate the deadline (30 minutes from now)
|
|
817
|
+
const deadline = Math.floor(Date.now() / 1000) + 30 * 60;
|
|
715
818
|
|
|
716
|
-
|
|
819
|
+
// If the token is native, we need to set the value
|
|
820
|
+
const value = token.tokenAddress === 'native' ? amount : 0;
|
|
821
|
+
const tokenAddress = token.tokenAddress === 'native' ? ethers.ZeroAddress : token.tokenAddress;
|
|
717
822
|
|
|
823
|
+
// Submit the transaction
|
|
718
824
|
const tx = await paymentContract.payment(
|
|
719
825
|
tradeId,
|
|
720
|
-
tokenAddress
|
|
826
|
+
tokenAddress,
|
|
721
827
|
toAddress,
|
|
722
828
|
amount,
|
|
723
|
-
|
|
829
|
+
protocolFeeAmount,
|
|
724
830
|
deadline,
|
|
725
|
-
{
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
)
|
|
729
|
-
|
|
730
|
-
this.logger.log(`Transfer transaction sent: ${tx.hash}`)
|
|
731
|
-
|
|
732
|
-
return ensureHexPrefix(tx.hash)
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
private getSigner(networkId: string) {
|
|
736
|
-
const rpcUrl = this.rpcMap.get(networkId)
|
|
831
|
+
{ value }
|
|
832
|
+
);
|
|
737
833
|
|
|
738
|
-
|
|
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
|
-
}
|
|
834
|
+
console.log(`Transfer transaction sent: ${tx.hash}`);
|
|
745
835
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
return paymentAddress
|
|
836
|
+
// Return the transaction hash with the 0x prefix
|
|
837
|
+
return `0x${tx.hash.replace(/^0x/, '')}`;
|
|
838
|
+
} catch (error) {
|
|
839
|
+
console.error('EVM payment error:', error);
|
|
840
|
+
throw error;
|
|
753
841
|
}
|
|
754
842
|
}
|
|
755
843
|
```
|
|
@@ -760,177 +848,4 @@ In case the target chain is Bitcoin, the transaction should have at least N + 1
|
|
|
760
848
|
|
|
761
849
|
Example implementation:
|
|
762
850
|
|
|
763
|
-
```
|
|
764
|
-
import * as bitcoin from 'bitcoinjs-lib'
|
|
765
|
-
import { ECPairFactory } from 'ecpair'
|
|
766
|
-
import * as ecc from 'tiny-secp256k1'
|
|
767
|
-
|
|
768
|
-
import { getTradeIdsHash, Token } from '@optimex-xyz/market-maker-sdk'
|
|
769
|
-
|
|
770
|
-
import { ITransferStrategy, TransferParams } from '../interfaces/transfer-strategy.interface'
|
|
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
|
-
])
|
|
798
|
-
|
|
799
|
-
constructor(private configService: ConfigService) {
|
|
800
|
-
this.privateKey = this.configService.getOrThrow<string>('PMM_BTC_PRIVATE_KEY')
|
|
801
|
-
bitcoin.initEccLib(ecc)
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
async transfer(params: TransferParams): Promise<string> {
|
|
805
|
-
const { toAddress, amount, token, tradeId } = params
|
|
806
|
-
|
|
807
|
-
const network = this.getNetwork(token.networkId)
|
|
808
|
-
const rpcUrl = this.getRpcUrl(token.networkId)
|
|
809
|
-
|
|
810
|
-
const txId = await this.sendBTC(this.privateKey, toAddress, amount, network, rpcUrl, token, [tradeId])
|
|
811
|
-
|
|
812
|
-
return ensureHexPrefix(txId)
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
private createPayment(publicKey: Uint8Array, network: bitcoin.Network) {
|
|
816
|
-
const p2tr = bitcoin.payments.p2tr({
|
|
817
|
-
internalPubkey: Buffer.from(publicKey.slice(1, 33)),
|
|
818
|
-
network,
|
|
819
|
-
})
|
|
820
|
-
|
|
821
|
-
return {
|
|
822
|
-
payment: p2tr,
|
|
823
|
-
keypair: this.ECPair.fromWIF(this.privateKey, network),
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
private async sendBTC(
|
|
828
|
-
privateKey: string,
|
|
829
|
-
toAddress: string,
|
|
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
|
-
}
|
|
842
|
-
|
|
843
|
-
const utxos = await this.getUTXOs(payment.address, rpcUrl)
|
|
844
|
-
if (utxos.length === 0) {
|
|
845
|
-
throw new Error(`No UTXOs found in ${token.networkSymbol} wallet`)
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const psbt = new bitcoin.Psbt({ network })
|
|
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
|
-
}
|
|
870
|
-
|
|
871
|
-
this.logger.log(`Total input: ${totalInput.toString()} ${token.tokenSymbol}`)
|
|
872
|
-
|
|
873
|
-
if (totalInput < amountInSatoshis) {
|
|
874
|
-
throw new Error(
|
|
875
|
-
`Insufficient balance in ${token.networkSymbol} wallet. ` +
|
|
876
|
-
`Need ${amountInSatoshis} satoshis, but only have ${totalInput} satoshis`
|
|
877
|
-
)
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const feeRate = await this.getFeeRate(rpcUrl)
|
|
881
|
-
const fee = BigInt(Math.ceil(200 * feeRate))
|
|
882
|
-
const changeAmount = totalInput - amountInSatoshis - fee
|
|
883
|
-
|
|
884
|
-
psbt.addOutput({
|
|
885
|
-
address: toAddress,
|
|
886
|
-
value: amountInSatoshis,
|
|
887
|
-
})
|
|
888
|
-
|
|
889
|
-
if (changeAmount > 546n) {
|
|
890
|
-
psbt.addOutput({
|
|
891
|
-
address: payment.address,
|
|
892
|
-
value: changeAmount,
|
|
893
|
-
})
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
const tradeIdsHash = getTradeIdsHash(tradeIds)
|
|
897
|
-
|
|
898
|
-
// Add OP_RETURN output with tradeIds hash
|
|
899
|
-
psbt.addOutput({
|
|
900
|
-
script: bitcoin.script.compile([bitcoin.opcodes['OP_RETURN'], Buffer.from(tradeIdsHash.slice(2), 'hex')]),
|
|
901
|
-
value: 0n,
|
|
902
|
-
})
|
|
903
|
-
|
|
904
|
-
const toXOnly = (pubKey: Uint8Array) => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33))
|
|
905
|
-
const tweakedSigner = keyPair.tweak(bitcoin.crypto.taggedHash('TapTweak', toXOnly(keyPair.publicKey)))
|
|
906
|
-
|
|
907
|
-
for (let i = 0; i < psbt.data.inputs.length; i++) {
|
|
908
|
-
psbt.signInput(i, tweakedSigner, [bitcoin.Transaction.SIGHASH_DEFAULT])
|
|
909
|
-
this.logger.log(`Input ${i} signed successfully`)
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
psbt.finalizeAllInputs()
|
|
913
|
-
|
|
914
|
-
const tx = psbt.extractTransaction()
|
|
915
|
-
const rawTx = tx.toHex()
|
|
916
|
-
|
|
917
|
-
const response = await axios.post(`${rpcUrl}/api/tx`, rawTx, {
|
|
918
|
-
headers: {
|
|
919
|
-
'Content-Type': 'text/plain',
|
|
920
|
-
},
|
|
921
|
-
})
|
|
922
|
-
|
|
923
|
-
return response.data
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
private async getUTXOs(address: string, rpcUrl: string): Promise<UTXO[]> {
|
|
927
|
-
const response = await axios.get<UTXO[]>(`${rpcUrl}/api/address/${address}/utxo`)
|
|
928
|
-
return response.data
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
private async getFeeRate(rpcUrl: string): Promise<number> {
|
|
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
|
|
851
|
+
```js
|
package/dist/index.js
CHANGED
|
@@ -80,7 +80,7 @@ var environments = {
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
production: {
|
|
83
|
-
backendUrl: "https://api.optimex.
|
|
83
|
+
backendUrl: "https://api.optimex.xyz",
|
|
84
84
|
rpcUrl: "https://bitfi-ledger-testnet.alt.technology",
|
|
85
85
|
routerAddress: "0x272599CE3602A49B580A5C4a4d3C1067E30248D2",
|
|
86
86
|
paymentAddressMap: {
|
|
@@ -3757,7 +3757,7 @@ var SolverService = class {
|
|
|
3757
3757
|
SubmitSettlementRequestSchema.parse(params);
|
|
3758
3758
|
const snakeCaseParams = convertToSnakeCase(params);
|
|
3759
3759
|
const response = await import_axios.default.post(
|
|
3760
|
-
`${this.baseURL}/market-maker/submit-settlement-tx`,
|
|
3760
|
+
`${this.baseURL}/v1/market-maker/submit-settlement-tx`,
|
|
3761
3761
|
snakeCaseParams,
|
|
3762
3762
|
{
|
|
3763
3763
|
headers: {
|
|
@@ -3785,7 +3785,7 @@ var SolverService = class {
|
|
|
3785
3785
|
*/
|
|
3786
3786
|
async getTradeDetail(tradeId) {
|
|
3787
3787
|
try {
|
|
3788
|
-
const response = await import_axios.default.get(`${this.baseURL}/market-maker/trades/${tradeId}`, {
|
|
3788
|
+
const response = await import_axios.default.get(`${this.baseURL}/v1/market-maker/trades/${tradeId}`, {
|
|
3789
3789
|
headers: {
|
|
3790
3790
|
"Content-Type": "application/json",
|
|
3791
3791
|
Accept: "application/json"
|
|
@@ -3852,7 +3852,7 @@ var TokenService = class {
|
|
|
3852
3852
|
*/
|
|
3853
3853
|
async getTokens() {
|
|
3854
3854
|
try {
|
|
3855
|
-
const response = await import_axios2.default.get(`${this.baseURL}/market-maker/tokens`, {
|
|
3855
|
+
const response = await import_axios2.default.get(`${this.baseURL}/v1/market-maker/tokens`, {
|
|
3856
3856
|
headers: {
|
|
3857
3857
|
Accept: "application/json"
|
|
3858
3858
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,7 @@ var environments = {
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
production: {
|
|
18
|
-
backendUrl: "https://api.optimex.
|
|
18
|
+
backendUrl: "https://api.optimex.xyz",
|
|
19
19
|
rpcUrl: "https://bitfi-ledger-testnet.alt.technology",
|
|
20
20
|
routerAddress: "0x272599CE3602A49B580A5C4a4d3C1067E30248D2",
|
|
21
21
|
paymentAddressMap: {
|
|
@@ -3692,7 +3692,7 @@ var SolverService = class {
|
|
|
3692
3692
|
SubmitSettlementRequestSchema.parse(params);
|
|
3693
3693
|
const snakeCaseParams = convertToSnakeCase(params);
|
|
3694
3694
|
const response = await axios.post(
|
|
3695
|
-
`${this.baseURL}/market-maker/submit-settlement-tx`,
|
|
3695
|
+
`${this.baseURL}/v1/market-maker/submit-settlement-tx`,
|
|
3696
3696
|
snakeCaseParams,
|
|
3697
3697
|
{
|
|
3698
3698
|
headers: {
|
|
@@ -3720,7 +3720,7 @@ var SolverService = class {
|
|
|
3720
3720
|
*/
|
|
3721
3721
|
async getTradeDetail(tradeId) {
|
|
3722
3722
|
try {
|
|
3723
|
-
const response = await axios.get(`${this.baseURL}/market-maker/trades/${tradeId}`, {
|
|
3723
|
+
const response = await axios.get(`${this.baseURL}/v1/market-maker/trades/${tradeId}`, {
|
|
3724
3724
|
headers: {
|
|
3725
3725
|
"Content-Type": "application/json",
|
|
3726
3726
|
Accept: "application/json"
|
|
@@ -3787,7 +3787,7 @@ var TokenService = class {
|
|
|
3787
3787
|
*/
|
|
3788
3788
|
async getTokens() {
|
|
3789
3789
|
try {
|
|
3790
|
-
const response = await axios2.get(`${this.baseURL}/market-maker/tokens`, {
|
|
3790
|
+
const response = await axios2.get(`${this.baseURL}/v1/market-maker/tokens`, {
|
|
3791
3791
|
headers: {
|
|
3792
3792
|
Accept: "application/json"
|
|
3793
3793
|
}
|