@optimex-xyz/market-maker-sdk 0.5.0-dev-4772ae2 → 0.5.0-dev-c02fd32

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,18 +1,17 @@
1
- # PMM SDK Integration Documentation
1
+ # PMM API Integration Documentation
2
2
 
3
- A comprehensive toolkit for implementing Private Market Makers (PMMs) in the cross-chain trading network. This guide covers the required integration points between PMMs and our solver backend, enabling cross-chain liquidity provision and settlement.
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 SDK Integration Documentation](#pmm-sdk-integration-documentation)
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. Repository Structure](#11-repository-structure)
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. Installation](#21-installation)
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. SDK Functions for PMMs](#4-sdk-functions-for-pmms)
48
- - [4.1. Function: getTokens](#41-function-gettokens)
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
- - [Example Code](#example-code)
51
- - [4.2. Function: submitSettlementTx](#42-function-submitsettlementtx)
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
- - [Example Implementation](#example-implementation-5)
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 repository contains everything needed to integrate your PMM with 's solver network:
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. Installation
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&session_id=12345
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
- import { Token, tokenService } from '@optimex-xyz/market-maker-sdk'
183
-
184
- export const IndicativeQuoteResponseSchema = z.object({
185
- sessionId: z.string(),
186
- pmmReceivingAddress: z.string(),
187
- indicativeQuote: z.string(),
188
- error: z.string().optional(),
189
- });
190
-
191
- export type IndicativeQuoteResponse = z.infer<
192
- typeof IndicativeQuoteResponseSchema
193
- >;
194
-
195
- async getIndicativeQuote(dto: GetIndicativeQuoteDto): Promise<IndicativeQuoteResponse> {
196
- const sessionId = dto.sessionId || this.generateSessionId()
197
-
198
- const [fromToken, toToken] = Promise.all([
199
- this.tokenService.getTokenByTokenId(dto.fromTokenId),
200
- this.tokenService.getTokenByTokenId(dto.toTokenId),
201
- ])
202
-
203
- const quote = this.calculateBestQuote()
204
-
205
- return {
206
- sessionId,
207
- pmmReceivingAddress,
208
- indicativeQuote: quote,
209
- error: '',
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=abcd1234&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
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": "abcd1234",
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
- import { Token, tokenService } from '@optimex-xyz/market-maker-sdk'
263
-
264
- export const GetCommitmentQuoteSchema = z.object({
265
- sessionId: z.string(),
266
- tradeId: z.string(),
267
- fromTokenId: z.string(),
268
- toTokenId: z.string(),
269
- amount: z.string(),
270
- fromUserAddress: z.string(),
271
- toUserAddress: z.string(),
272
- userDepositTx: z.string(),
273
- userDepositVault: z.string(),
274
- tradeDeadline: z.string(),
275
- scriptDeadline: z.string(),
276
- });
277
-
278
- export class GetCommitmentQuoteDto extends createZodDto(
279
- GetCommitmentQuoteSchema
280
- ) {}
281
-
282
- async getCommitmentQuote(dto: GetCommitmentQuoteDto): Promise<CommitmentQuoteResponse> {
283
- const session = await this.sessionRepo.findById(dto.sessionId)
284
-
285
- const [fromToken, toToken] = await Promise.all([
286
- tokenService.getTokenByTokenId(dto.fromTokenId),
287
- tokenService.getTokenByTokenId(dto.toTokenId),
288
- ])
289
-
290
- const quote = this.calculateBestQuote(...)
291
-
292
- await this.tradeService.createTrade({ tradeId: dto.tradeId })
293
-
294
- await this.tradeService.updateTradeQuote(dto.tradeId, { commitmentQuote: quote })
295
-
296
- return {
297
- tradeId: dto.tradeId,
298
- commitmentQuote: quote,
299
- error: '',
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=abcd1234&committed_quote=987654321000000000&trade_deadline=1696012800&script_deadline=1696016400
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": "abcd1234",
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
- ```ts
347
- import {
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 { tradeId } = trade
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
- // Get data directly from l2 contract or using routerService ( wrapper of l2 contract )
369
- const [presigns, tradeData] = await Promise.all([
370
- routerService.getPresigns(tradeId),
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
- const { toChain } = tradeData.tradeInfo
375
- const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800)
398
+ // Calculate a deadline (30 minutes from now)
399
+ const deadline = Math.floor(Date.now() / 1000) + 1800;
376
400
 
377
- const pmmId = ... // hexString
378
- const pmmPresign = presigns.find((t) => t.pmmId === pmmId)
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
- // get amountOut from the committed quote
384
- const amountOut = BigInt(dto.committedQuote)
404
+ // Get the presigns and trade data from tradeDetails
405
+ const { from_token, to_token } = tradeDetails.data;
385
406
 
386
- const commitInfoHash = getCommitInfoHash(
387
- pmmPresign.pmmId,
388
- pmmPresign.pmmRecvAddress,
389
- toChain[1],
390
- toChain[2],
391
- amountOut,
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
- const signature = await getSignature(
400
- this.pmmWallet,
401
- this.provider,
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
- tradeId: tradeId,
423
+ return res.status(200).json({
424
+ trade_id,
411
425
  signature,
412
- deadline: Number(deadline),
413
- error: '',
414
- }
415
- } catch (error: any) {
416
- // Handle error
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=abcd1234&trade_deadline=1696012800&script_deadline=1696016400&chosen=true
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": "abcd1234",
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
- ```ts
465
- export const AckSettlementSchema = z.object({
466
- tradeId: z.string(),
467
- tradeDeadline: z.string(),
468
- scriptDeadline: z.string(),
469
- chosen: z.string().refine((val) => val === 'true' || val === 'false', {
470
- message: "chosen must be 'true' or 'false'",
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
- export class AckSettlementDto extends createZodDto(AckSettlementSchema) {}
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
- async ackSettlement(dto: AckSettlementDto, trade: Trade): Promise<AckSettlementResponseDto> {
477
- try {
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: any) {
484
- if (error instanceof HttpException) {
485
- throw error
486
- }
487
- throw new BadRequestException(error.message)
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=abcd1234&protocol_fee_amount=1000000000000000&trade_deadline=1696012800&script_deadline=1696016400
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": "abcd1234",
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
- ```ts
536
- export const SignalPaymentSchema = z.object({
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
- // enqueue transfer with dto and trade
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
- return {
550
- tradeId: dto.tradeId,
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: any) {
555
- if (error instanceof HttpException) {
556
- throw error
557
- }
558
- throw new BadRequestException(error.message)
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. SDK Functions for PMMs
606
+ ## 4. Solver API Endpoints for PMMs
607
+
564
608
 
565
- These SDK functions facilitate PMM-Solver communication and are essential for implementing the required backend APIs.
609
+ These API endpoints are provided by the Solver backend for PMMs to retrieve token information and submit settlement data.
566
610
 
567
- ### 4.1. Function: getTokens
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 all supported tokens across different networks.
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
- #### Example Code
626
+ ```
627
+ GET /v1/market-maker/tokens
628
+ ```
574
629
 
575
- ```ts
576
- import { tokenService } from '@optimex-xyz/market-maker-sdk'
630
+ #### Expected Response
577
631
 
578
- tokenService.getTokens()
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. Function: submitSettlementTx
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 raw transaction data (in hex) representing the settlement.
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 Implementation
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
- const signature = await getSignature(
623
- this.pmmWallet,
624
- this.provider,
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
- 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
- }
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
- const response = await this.solverSerivce.submitSettlementTx(requestPayload)
751
+ #### Expected Response
643
752
 
644
- return response
645
- } catch (error: any) {
646
- this.logger.error('submit settlement error', error.stack)
753
+ - **HTTP Status**: `200 OK`
754
+ - **Response Body** (JSON):
647
755
 
648
- throw error // Re-throw the error for the queue to handle
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
- ## 5. PMM Making Payment
768
+ ### 4.3. Endpoint: `/v1/market-maker/trades/:tradeId`
660
769
 
661
- ```ts
662
- import { Token } from '@optimex-xyz/market-maker-sdk'
770
+ #### Description
663
771
 
664
- export interface TransferParams {
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
- export interface ITransferStrategy {
672
- transfer(params: TransferParams): Promise<string>
673
- }
674
- ```
774
+ #### Request Parameters
675
775
 
676
- ### 5.1. EVM
776
+ - **HTTP Method**: `GET`
777
+ - **Path Parameters**:
778
+ - `tradeId` (string): The unique identifier for the trade to retrieve.
677
779
 
678
- 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.
780
+ #### Example Request
679
781
 
680
- Example implementation:
782
+ ```
783
+ GET /v1/market-maker/trades/0x650e2c921a85eb0b8831ff838d4d98c0a5cd2ede5c0cb6bb4a15969f51c75424
784
+ ```
681
785
 
682
- ```ts
683
- import { config, ensureHexPrefix, ERC20__factory, Payment__factory, routerService } from '@optimex-xyz/market-maker-sdk'
786
+ #### Expected Response
684
787
 
685
- import { ITransferStrategy, TransferParams } from '../interfaces/transfer-strategy.interface'
788
+ - **HTTP Status**: `200 OK`
789
+ - **Response Body** (JSON): See the detailed response format in the updated PMM Integration API Documentation.
686
790
 
687
- @Injectable()
688
- export class EVMTransferStrategy implements ITransferStrategy {
689
- private pmmPrivateKey: string
791
+ ## 5. PMM Making Payment
690
792
 
691
- private routerService = routerService
692
- private readonly rpcMap = new Map<string, string>([['ethereum_sepolia', 'https://eth-sepolia.public.blastapi.io']])
793
+ ### 5.1. EVM
693
794
 
694
- constructor(private configService: ConfigService) {
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
- async transfer(params: TransferParams): Promise<string> {
699
- const { toAddress, amount, token, tradeId } = params
700
- const { tokenAddress, networkId } = token
797
+ Example implementation:
701
798
 
702
- const signer = this.getSigner(networkId)
799
+ ```js
800
+ const { ethers } = require('ethers');
703
801
 
704
- const paymentAddress = this.getPaymentAddress(networkId)
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
- if (tokenAddress !== 'native') {
707
- // allowance with ERC20
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
- const paymentContract = Payment__factory.connect(paymentAddress, signer)
812
+ // Get the payment contract address
813
+ const paymentAddress = getPaymentAddressForNetwork(token.networkId);
711
814
 
712
- const feeDetail = await this.routerService.getFeeDetails(tradeId)
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
- const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60)
822
+ // Calculate the deadline (30 minutes from now)
823
+ const deadline = Math.floor(Date.now() / 1000) + 30 * 60;
715
824
 
716
- const decoder = errorDecoder()
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 === 'native' ? ZeroAddress : tokenAddress,
832
+ tokenAddress,
721
833
  toAddress,
722
834
  amount,
723
- feeDetail.totalAmount,
835
+ protocolFeeAmount,
724
836
  deadline,
725
- {
726
- value: tokenAddress === 'native' ? amount : 0n,
727
- }
728
- )
837
+ { value }
838
+ );
729
839
 
730
- this.logger.log(`Transfer transaction sent: ${tx.hash}`)
840
+ console.log(`Transfer transaction sent: ${tx.hash}`);
731
841
 
732
- return ensureHexPrefix(tx.hash)
733
- }
734
-
735
- private getSigner(networkId: string) {
736
- const rpcUrl = this.rpcMap.get(networkId)
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
- ```ts
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
- 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
- ])
864
+ async function makeBitcoinPayment(params) {
865
+ const { toAddress, amount, token, tradeId } = params
866
+ const ECPair = ECPairFactory(ecc)
798
867
 
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
868
+ // Set up Bitcoin library
869
+ bitcoin.initEccLib(ecc)
806
870
 
807
- const network = this.getNetwork(token.networkId)
808
- const rpcUrl = this.getRpcUrl(token.networkId)
871
+ // Get network configuration
872
+ const network = getNetwork(token.networkId)
873
+ const rpcUrl = getRpcUrl(token.networkId)
809
874
 
810
- const txId = await this.sendBTC(this.privateKey, toAddress, amount, network, rpcUrl, token, [tradeId])
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
- return ensureHexPrefix(txId)
882
+ if (!payment.address) {
883
+ throw new Error('Could not generate address')
813
884
  }
814
885
 
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
- }
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
- 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
- }
892
+ // Create and sign transaction
893
+ const psbt = new bitcoin.Psbt({ network })
894
+ let totalInput = 0n
842
895
 
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`)
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 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
- }
902
+ const internalKey = Buffer.from(keyPair.publicKey.slice(1, 33))
870
903
 
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
- }
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
- const feeRate = await this.getFeeRate(rpcUrl)
881
- const fee = BigInt(Math.ceil(200 * feeRate))
882
- const changeAmount = totalInput - amountInSatoshis - fee
914
+ totalInput += BigInt(utxo.value)
915
+ }
883
916
 
884
- psbt.addOutput({
885
- address: toAddress,
886
- value: amountInSatoshis,
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
- if (changeAmount > 546n) {
890
- psbt.addOutput({
891
- address: payment.address,
892
- value: changeAmount,
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
- const tradeIdsHash = getTradeIdsHash(tradeIds)
929
+ // Add recipient output
930
+ psbt.addOutput({
931
+ address: toAddress,
932
+ value: amount,
933
+ })
897
934
 
898
- // Add OP_RETURN output with tradeIds hash
935
+ // Add change output if needed
936
+ if (changeAmount > 546n) {
899
937
  psbt.addOutput({
900
- script: bitcoin.script.compile([bitcoin.opcodes['OP_RETURN'], Buffer.from(tradeIdsHash.slice(2), 'hex')]),
901
- value: 0n,
938
+ address: payment.address,
939
+ value: changeAmount,
902
940
  })
941
+ }
903
942
 
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()
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
- const tx = psbt.extractTransaction()
915
- const rawTx = tx.toHex()
961
+ psbt.finalizeAllInputs()
916
962
 
917
- const response = await axios.post(`${rpcUrl}/api/tx`, rawTx, {
918
- headers: {
919
- 'Content-Type': 'text/plain',
920
- },
921
- })
963
+ // Extract transaction
964
+ const tx = psbt.extractTransaction()
965
+ const rawTx = tx.toHex()
922
966
 
923
- return response.data
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
- 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
- }
974
+ return response.data // Transaction ID
975
+ }
930
976
 
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
977
+ ```