npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@optimex-xyz/market-maker-sdk

v0.9.1

Published

> **CHANGELOG (v0.8.0)**: > > - **Breaking Changes:** > - Update to get router contract from protocol fetcher > - Use consistent trade_id across all protocol > - Add `user_receiving_address`, `user_refund_pubkey`, `from_user_address` to `indicative_

Readme

PMM API Integration Documentation

CHANGELOG (v0.8.0):

  • Breaking Changes:
    • Update to get router contract from protocol fetcher
    • Use consistent trade_id across all protocol
    • Add user_receiving_address, user_refund_pubkey, from_user_address to indicative_quote api requirement as optional fields.
  • Upgrade Notes:
    • For PMMs older version you can update to v0.8.0 without needing to change anything
    • If you need to use the Router, please use the values provided in the environment configuration section

Note: If you prefer using the SDK instead of direct API integration, please refer to the PMM SDK Integration Guide.

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.

Table of Contents

Smart Contract Integration

Contract Addresses

Testnet

| Contract | Address | | -------- | -------------------------------------------- | | Signer | 0xA89F5060B810F3b6027D7663880c43ee77A865C7 | | Router | 0x31C88ebd9E430455487b6a5c8971e8eF63e97ED4 | | Payment | 0x7387DcCfE2f1D5F80b4ECDF91eF58541517e90D2 |

Mainnet

| Contract | Address | | -------- | -------------------------------------------- | | Signer | 0xCF9786F123F1071023dB8049808C223e94c384be | | Router | 0x1e878cCa765a8aAFEBecCa672c767441b4859634 | | Payment | 0x0A497AC4261E37FA4062762C23Cf3cB642C839b8 |

1. Overview

The PMM integration with Optimex involves bidirectional API communication:

  1. PMM-Provided APIs: Endpoints that PMMs must implement to receive requests from the Solver
  2. Solver-Provided APIs: Endpoints that the Solver provides for PMMs to call

1.1. Integration Flow

sequenceDiagram
    participant User
    participant Solver
    participant PMM
    participant Chain

    Note over User,Chain: Phase 1: Indicative Quote
    User->>Solver: Request quote
    Solver->>PMM: GET /indicative-quote
    PMM-->>Solver: Return indicative quote
    Solver-->>User: Show quote

    Note over User,Chain: Phase 2: Commitment
    User->>Solver: Accept quote
    Solver->>PMM: GET /commitment-quote
    PMM-->>Solver: Return commitment quote

    Note over User,Chain: Phase 3: Settlement
    Solver->>PMM: GET /settlement-signature
    PMM-->>Solver: Return signature
    Solver->>PMM: POST /ack-settlement
    PMM-->>Solver: Acknowledge settlement
    Solver->>PMM: POST /signal-payment
    PMM-->>Solver: Acknowledge signal
    PMM->>Chain: Execute settlement (transfer)
    PMM->>Solver: POST /v1/market-maker/submit-settlement-tx
    Solver-->>PMM: Confirm settlement submission

2. Quick Start

2.1. API Environments

| Environment | Description | | ------------ | --------------------------------------------------------------------------- | | dev | internal environment with test networks and development services | | staging | Staging environment with test networks and staging services | | prelive | Pre production environment with mainnet networks for testing before release | | production | Production environment with mainnet networks and production services |

Optimex L2 Testnet

Ethereum Sepolia

Optimex L2 Mainnet

Ethereum Mainnet

Note: The prelive and production environments use the same contract addresses. The difference is in the backend services and configuration that interact with these contracts.

3. PMM Backend APIs

These are the APIs that PMMs must implement for Solver integration. These endpoints allow Solvers to communicate with your PMM service.

3.1. Endpoint: /indicative-quote

Description

Provides an indicative quote for the given token pair and trade amount. The quote is used for informational purposes before a commitment is made.

Request Parameters

  • HTTP Method: GET
  • Query Parameters:
    • from_token_id (string): The ID of the source token.
    • to_token_id (string): The ID of the destination token.
    • amount (string): The amount of the source token to be traded, represented as a string in base 10 to accommodate large numbers.
    • session_id (string, optional): A unique identifier for the session.
    • deposited (boolean, optional): Whether the deposit has been confirmed. This allows the PMM to decide the returned quote.
    • trade_timeout (string, optional): The deadline when user is expected to receive tokens from PMM in UNIX timestamp. We expect the trade to be completed before this timeout. But if not, some actions can still be taken.
    • script_timeout (string, optional): The hard timeout for the trade, UNIX timestamp. After this timeout, the trade will not be processed further.
    • from_user_address (string, optional): The user's address from which the input token will be sent from.
    • user_receiving_address (string, optional): The user's address to which the output token will be sent to.
    • user_refund_pubkey (string, optional): The user's public key to which the refund will be sent.

Example Request

GET /indicative-quote?from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "session_id": "12345",
  "pmm_receiving_address": "0xReceivingAddress",
  "indicative_quote": "123456789000000000",
  "error": "" // Empty if no error
}
  • session_id (string): The session ID associated with the request.
  • pmm_receiving_address (string): The receiving address where the user will send the from_token.
  • indicative_quote (string): The indicative quote value, represented as a string. Should be treated as a BigInt in your implementation.
  • error (string): Error message, if any (empty if no error).
import crypto from 'crypto'
import { tokenService } from '@optimex-xyz/market-maker-sdk'

// In-memory session storage (use Redis in production)
const sessionStore = new Map()

function generateSessionId() {
  return crypto.randomBytes(16).toString('hex')
}

function getPmmAddressByNetworkType(token) {
  switch (token.networkType.toUpperCase()) {
    case 'EVM':
      return process.env.PMM_EVM_ADDRESS
    case 'BTC':
    case 'TBTC':
      return process.env.PMM_BTC_ADDRESS
    case 'SOLANA':
      return process.env.PMM_SOLANA_ADDRESS
    default:
      throw new Error(`Unsupported network type: ${token.networkType}`)
  }
}

async function getIndicativeQuote(req, res) {
  try {
    const { from_token_id, to_token_id, amount, session_id } = req.query

    // Generate a session ID if not provided
    const sessionId = session_id || generateSessionId()

    // Fetch token information using SDK tokenService
    const [fromToken, toToken] = await Promise.all([
      tokenService.getTokenByTokenId(from_token_id),
      tokenService.getTokenByTokenId(to_token_id),
    ])

    if (!fromToken) {
      return res.status(400).json({
        session_id: sessionId,
        pmm_receiving_address: '',
        indicative_quote: '0',
        error: `From token not found: ${from_token_id}`,
      })
    }
    if (!toToken) {
      return res.status(400).json({
        session_id: sessionId,
        pmm_receiving_address: '',
        indicative_quote: '0',
        error: `To token not found: ${to_token_id}`,
      })
    }

    // Validate amount (implement your own validation logic)
    const amountBigInt = BigInt(amount)
    validateIndicativeAmount(amountBigInt, fromToken)

    // Calculate the quote (implementation specific to your PMM)
    const quote = await calculateBestQuote({
      amountIn: amount,
      fromTokenId: from_token_id,
      toTokenId: to_token_id,
      isCommitment: false,
    })

    // Get the receiving address based on network type
    const pmmReceivingAddress = getPmmAddressByNetworkType(fromToken)

    // Save session data for later use in commitment quote
    sessionStore.set(sessionId, {
      fromToken: from_token_id,
      toToken: to_token_id,
      amount: amount,
      pmmReceivingAddress: pmmReceivingAddress,
      indicativeQuote: quote,
    })

    return res.status(200).json({
      session_id: sessionId,
      pmm_receiving_address: pmmReceivingAddress,
      indicative_quote: quote.toString(),
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      session_id: req.query.session_id || '',
      pmm_receiving_address: '',
      indicative_quote: '0',
      error: error.message,
    })
  }
}

3.2. Endpoint: /commitment-quote

Description

Provides a commitment quote for a specific trade, representing a firm commitment to proceed under the quoted conditions.

Request Parameters

  • HTTP Method: GET
  • Query Parameters:
    • session_id (string): A unique identifier for the session.
    • trade_id (string): The unique identifier for the trade. Example format: 0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328.
    • from_token_id (string): The ID of the source token.
    • to_token_id (string): The ID of the destination token.
    • amount (string): The amount of the source token to be traded, in base 10. This should be treated as a BigInt in your implementation.
    • from_user_address (string): The address of the user initiating the trade.
    • to_user_address (string): The address where the user will receive the to_token.
    • user_deposit_tx (string): The transaction hash where the user deposited their funds.
    • user_deposit_vault (string): The vault where the user's deposit is kept.
    • 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.
    • 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.

Example Request

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

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
  "commitment_quote": "987654321000000000",
  "error": "" // Empty if no error
}
  • trade_id (string): The trade ID associated with the request.
  • commitment_quote (string): The committed quote value, represented as a string. Should be treated as a BigInt in your implementation.
  • error (string): Error message, if any (empty if no error).
import { tokenService } from '@optimex-xyz/market-maker-sdk'

// Session store (use Redis in production)
const sessionStore = new Map()

async function getCommitmentQuote(req, res) {
  try {
    const {
      session_id,
      trade_id,
      from_token_id,
      to_token_id,
      amount,
      from_user_address,
      to_user_address,
      user_deposit_tx,
      user_deposit_vault,
      trade_deadline,
      script_deadline,
    } = req.query

    // Validate the session exists
    const session = sessionStore.get(session_id)
    if (!session) {
      return res.status(400).json({
        trade_id,
        commitment_quote: '0',
        error: 'Session expired during processing',
      })
    }

    // Fetch token information using SDK tokenService
    const [fromToken, toToken] = await Promise.all([
      tokenService.getTokenByTokenId(from_token_id),
      tokenService.getTokenByTokenId(to_token_id),
    ])

    if (!fromToken) {
      return res.status(400).json({
        trade_id,
        commitment_quote: '0',
        error: `From token not found: ${from_token_id}`,
      })
    }
    if (!toToken) {
      return res.status(400).json({
        trade_id,
        commitment_quote: '0',
        error: `To token not found: ${to_token_id}`,
      })
    }

    // Validate commitment amount (implement your own validation logic)
    validateCommitmentAmount(BigInt(amount), fromToken)

    // Delete any existing trade with the same ID (handle retries)
    await tradeRepository.delete(trade_id)

    // Calculate the final quote (implementation specific to your PMM)
    const quote = await calculateBestQuote({
      amountIn: amount,
      fromTokenId: from_token_id,
      toTokenId: to_token_id,
      isCommitment: true, // Use commitment pricing
    })

    // Store the trade in the database
    await tradeRepository.create({
      tradeId: trade_id,
      fromTokenId: from_token_id,
      toTokenId: to_token_id,
      fromUser: from_user_address,
      toUser: to_user_address,
      amount: amount,
      fromNetworkId: fromToken.networkId,
      toNetworkId: toToken.networkId,
      userDepositTx: user_deposit_tx,
      userDepositVault: user_deposit_vault,
      tradeDeadline: trade_deadline,
      scriptDeadline: script_deadline,
      tradeType: 'SWAP',
      commitmentQuote: quote.toString(),
    })

    return res.status(200).json({
      trade_id,
      commitment_quote: quote.toString(),
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      trade_id: req.query.trade_id || '',
      commitment_quote: '0',
      error: error.message,
    })
  }
}

3.3. Endpoint: /liquidation-quote

Description

Provides a firm commitment quote for a liquidation trade. This endpoint is called after the user deposits funds and represents a binding commitment to execute the liquidation at the quoted rate.

Request Parameters

  • HTTP Method: GET
  • Query Parameters:
    • session_id (string): Session identifier from the indicative quote.
    • trade_id (string): Unique trade identifier.
    • from_token_id (string): The ID of the source token.
    • to_token_id (string): The ID of the destination token.
    • amount (string): The amount of the source token to be traded, in base 10. This should be treated as a BigInt in your implementation.
    • payment_metadata (string, optional): Hex string encoded data for smart contract payment method.
    • from_user_address (string): The user's address from which the input token will be sent.
    • to_user_address (string): The user's address to which the output token will be sent.
    • user_deposit_tx (string): Transaction hash of user's deposit.
    • user_deposit_vault (string): Vault containing user's deposit.
    • trade_deadline (string): Expected payment deadline (UNIX timestamp).
    • script_deadline (string): Withdrawal deadline if unpaid (UNIX timestamp).

Example Request

GET /liquidation-quote?session_id=12345&trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&from_token_id=ETH&to_token_id=BTC&amount=1000000000000000000&from_user_address=0xUserAddress&to_user_address=bc1p68q6hew27ljf4ghvlnwqz0fq32qg7tsgc7jr5levfy8r74p5k52qqphk07&user_deposit_tx=0xDepositTxHash&user_deposit_vault=VaultData&trade_deadline=1696012800&script_deadline=1696016400

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
  "liquidation_quote": "987654321000000000",
  "error": ""
}
  • trade_id (string): The trade ID associated with the request.
  • liquidation_quote (string): Firm committed quote - PMM must honor this price. Should be treated as a BigInt in your implementation.
  • error (string): Error message, if any (empty if no error).
import { tokenService } from '@optimex-xyz/market-maker-sdk'

// Session store (use Redis in production)
const sessionStore = new Map()

async function getLiquidationQuote(req, res) {
  try {
    const {
      session_id,
      trade_id,
      from_token_id,
      to_token_id,
      amount,
      payment_metadata,
      from_user_address,
      to_user_address,
      user_deposit_tx,
      user_deposit_vault,
      trade_deadline,
      script_deadline,
    } = req.query

    // Validate the session exists
    const session = sessionStore.get(session_id)
    if (!session) {
      return res.status(400).json({
        trade_id,
        liquidation_quote: '0',
        error: 'Session expired during processing',
      })
    }

    // Fetch token information using SDK tokenService
    const [fromToken, toToken] = await Promise.all([
      tokenService.getTokenByTokenId(from_token_id),
      tokenService.getTokenByTokenId(to_token_id),
    ])

    if (!fromToken) {
      return res.status(400).json({
        trade_id,
        liquidation_quote: '0',
        error: `From token not found: ${from_token_id}`,
      })
    }
    if (!toToken) {
      return res.status(400).json({
        trade_id,
        liquidation_quote: '0',
        error: `To token not found: ${to_token_id}`,
      })
    }

    // Validate commitment amount (implement your own validation logic)
    validateCommitmentAmount(BigInt(amount), fromToken)

    // Delete any existing trade with the same ID (handle retries)
    await tradeRepository.delete(trade_id)

    // Calculate the firm liquidation quote (implementation specific to your PMM)
    const quote = await calculateBestQuote({
      amountIn: amount,
      fromTokenId: from_token_id,
      toTokenId: to_token_id,
      isCommitment: true, // Use commitment pricing for liquidation
    })

    // Store the trade in the database with LENDING trade type
    await tradeRepository.create({
      tradeId: trade_id,
      fromTokenId: from_token_id,
      toTokenId: to_token_id,
      fromUser: from_user_address,
      toUser: to_user_address,
      amount: amount,
      fromNetworkId: fromToken.networkId,
      toNetworkId: toToken.networkId,
      userDepositTx: user_deposit_tx,
      userDepositVault: user_deposit_vault,
      tradeDeadline: trade_deadline,
      scriptDeadline: script_deadline,
      tradeType: 'LENDING', // Liquidation trades use LENDING type
      metadata: {
        paymentMetadata: payment_metadata, // Store payment metadata for liquidation
      },
      commitmentQuote: quote.toString(),
    })

    return res.status(200).json({
      trade_id,
      liquidation_quote: quote.toString(),
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      trade_id: req.query.trade_id || '',
      liquidation_quote: '0',
      error: error.message,
    })
  }
}

3.4. Endpoint: /settlement-signature

Description

Returns a signature from the PMM to confirm the settlement quote, required to finalize the trade.

Request Parameters

  • HTTP Method: GET
  • Query Parameters:
    • trade_id (string): The unique identifier for the trade. Example format: 0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a.
    • committed_quote (string): The committed quote value in base 10. This should be treated as a BigInt in your implementation.
    • trade_deadline (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
    • script_deadline (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.

Example Request

GET /settlement-signature?trade_id=0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a&committed_quote=987654321000000000&trade_deadline=1696012800&script_deadline=1696016400

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "trade_id": "0x3d09b8eb94466bffa126aeda68c8c0f330633a7d0058f57269d795530415498a",
  "signature": "0xSignatureData",
  "deadline": 1696012800,
  "error": "" // Empty if no error
}
  • trade_id (string): The trade ID associated with the request.
  • signature (string): The signature provided by the PMM.
  • deadline (integer): The UNIX timestamp (in seconds) indicating the PMM's expected payment deadline.
  • error (string): Error message, if any (empty if no error).
import {
  getCommitInfoHash,
  getSignature,
  routerService,
  SignatureType,
  signerService,
  tokenService,
} from '@optimex-xyz/market-maker-sdk'

import { ethers } from 'ethers'

// Helper function to encode string to hex
const l2Encode = (info) => {
  if (/^0x[0-9a-fA-F]*$/.test(info)) {
    return info
  }
  return '0x' + Buffer.from(info, 'utf8').toString('hex')
}

// Helper function to decode hex to string
const l2Decode = (hex) => {
  if (!hex.startsWith('0x')) return hex
  return Buffer.from(hex.slice(2), 'hex').toString('utf8')
}

async function getSettlementSignature(req, res) {
  try {
    const { trade_id, committed_quote, trade_deadline, script_deadline } = req.query

    // Fetch the trade from the database
    const trade = await tradeRepository.findById(trade_id)
    if (!trade) {
      return res.status(400).json({
        trade_id,
        signature: '',
        deadline: 0,
        error: 'Trade not found',
      })
    }

    // Fetch presigns and trade data from router service
    const [presigns, tradeData] = await Promise.all([
      routerService.getSettlementPresigns(trade_id),
      routerService.getTradeData(trade_id),
    ])

    const { toChain, fromChain } = tradeData.tradeInfo

    // Get the from token to determine PMM receiving address
    const fromToken = await tokenService.getToken(l2Decode(fromChain[1]), l2Decode(fromChain[2]))
    const pmmAddress = getPmmAddressByNetworkType(fromToken) // Your PMM address based on network type

    // Calculate a deadline (30 minutes from now)
    const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800)

    // Get PMM ID (should be hex encoded)
    const pmmId = '0x' + Buffer.from(process.env.PMM_ID, 'utf8').toString('hex')

    // Find PMM presign and validate receiving address
    const pmmPresign = presigns.find((t) => t.pmmId === pmmId)
    if (!pmmPresign) {
      return res.status(400).json({
        trade_id,
        signature: '',
        deadline: 0,
        error: 'PMM presign not found',
      })
    }

    // Validate that the presign receiving address matches expected PMM address
    if (l2Decode(pmmPresign.pmmRecvAddress).toLowerCase() !== pmmAddress.toLowerCase()) {
      return res.status(400).json({
        trade_id,
        signature: '',
        deadline: 0,
        error: 'PMM receiving address mismatch',
      })
    }

    const amountOut = BigInt(committed_quote)

    // Create commitment info hash using SDK function
    const commitInfoHash = getCommitInfoHash(
      pmmId,
      l2Encode(pmmAddress),
      toChain[1], // destination chain
      toChain[2], // destination token address
      amountOut,
      deadline
    )

    // Get signer address and domain for EIP-712 signature
    const signerAddress = await routerService.getSigner()
    const domain = await signerService.getDomain()

    // Set up provider and wallet
    const provider = new ethers.JsonRpcProvider(process.env.RPC_URL)
    const pmmWallet = new ethers.Wallet(process.env.PMM_PRIVATE_KEY, provider)

    // Generate signature using SDK function
    const signature = await getSignature(
      pmmWallet,
      provider,
      signerAddress,
      trade_id,
      commitInfoHash,
      SignatureType.VerifyingContract,
      domain
    )

    return res.status(200).json({
      trade_id,
      signature,
      deadline: Number(deadline),
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      trade_id: req.query.trade_id || '',
      signature: '',
      deadline: 0,
      error: error.message,
    })
  }
}

// Helper function to get PMM address based on network type
function getPmmAddressByNetworkType(token) {
  switch (token.networkType.toUpperCase()) {
    case 'EVM':
      return process.env.PMM_EVM_ADDRESS
    case 'BTC':
    case 'TBTC':
      return process.env.PMM_BTC_ADDRESS
    case 'SOLANA':
      return process.env.PMM_SOLANA_ADDRESS
    default:
      throw new Error(`Unsupported network type: ${token.networkType}`)
  }
}

3.5. Endpoint: /ack-settlement

Description

Used by the solver to acknowledge to the PMM about a successful settlement, indicating whether the PMM is selected.

Request Parameters

  • HTTP Method: POST
  • Form Parameters:
    • trade_id (string): The unique identifier for the trade. Example format: 0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0.
    • trade_deadline (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
    • script_deadline (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
    • chosen (string): "true" if the PMM is selected, "false" otherwise.

Example Request

POST /ack-settlement
Content-Type: application/x-www-form-urlencoded

trade_id=0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0&trade_deadline=1696012800&script_deadline=1696016400&chosen=true

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "trade_id": "0x024be4dae899989e0c3d9b4459e5811613bcd04016dc56529f16a19d2a7724c0",
  "status": "acknowledged",
  "error": "" // Empty if no error
}
  • trade_id (string): The trade ID associated with the request.
  • status (string): Status of the acknowledgment (always "acknowledged").
  • error (string): Error message, if any (empty if no error).
// Trade status enum
const TradeStatus = {
  PENDING: 'PENDING',
  QUOTE_PROVIDED: 'QUOTE_PROVIDED',
  COMMITTED: 'COMMITTED',
  SELECTED: 'SELECTED',
  SETTLING: 'SETTLING',
  COMPLETED: 'COMPLETED',
  FAILED: 'FAILED',
}

async function ackSettlement(req, res) {
  try {
    const { trade_id, trade_deadline, script_deadline, chosen } = req.body

    // Fetch the trade from the database
    const trade = await tradeRepository.findById(trade_id)
    if (!trade) {
      return res.status(400).json({
        trade_id,
        status: 'error',
        error: 'Trade not found',
      })
    }

    // Update trade status based on whether PMM was chosen
    const isChosen = chosen === 'true'
    const newStatus = isChosen ? TradeStatus.SELECTED : TradeStatus.FAILED
    const failureReason = isChosen ? undefined : 'PMM not chosen for settlement'

    await tradeRepository.updateStatus(trade_id, newStatus, failureReason)

    return res.status(200).json({
      trade_id,
      status: 'acknowledged',
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      trade_id: req.body.trade_id || '',
      status: 'error',
      error: error.message,
    })
  }
}

3.6. Endpoint: /signal-payment

Description

Used by the solver to signal the chosen PMM to start submitting their payment.

Request Parameters

  • HTTP Method: POST
  • Form Parameters:
    • trade_id (string): The unique identifier for the trade. Example format: 0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328.
    • trade_deadline (string): The UNIX timestamp (in seconds) by which the user expects to receive payment.
    • script_deadline (string): The UNIX timestamp (in seconds) after which the user can withdraw their deposit if not paid.
    • 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.

Example Request

POST /signal-payment
Content-Type: application/x-www-form-urlencoded

trade_id=0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328&total_fee_amount=1000000000000000&trade_deadline=1696012800&script_deadline=1696016400

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "trade_id": "0x3bfe2fc4889a98a39b31b348e7b212ea3f2bea63fd1ea2e0c8ba326433677328",
  "status": "acknowledged",
  "error": "" // Empty if no error
}
  • trade_id (string): The trade ID associated with the request.
  • status (string): Status of the acknowledgment (always "acknowledged").
  • error (string): Error message, if any (empty if no error).
import { tokenService } from '@optimex-xyz/market-maker-sdk'

// Trade status enum
const TradeStatus = {
  PENDING: 'PENDING',
  QUOTE_PROVIDED: 'QUOTE_PROVIDED',
  COMMITTED: 'COMMITTED',
  SELECTED: 'SELECTED',
  SETTLING: 'SETTLING',
  COMPLETED: 'COMPLETED',
  FAILED: 'FAILED',
}

// Queue names for different network types
const SETTLEMENT_QUEUES = {
  EVM: 'settlement:evm:transfer',
  BTC: 'settlement:btc:transfer',
  SOLANA: 'settlement:solana:transfer',
}

function getQueueNameByNetworkType(networkType) {
  switch (networkType.toUpperCase()) {
    case 'EVM':
      return SETTLEMENT_QUEUES.EVM
    case 'BTC':
    case 'TBTC':
      return SETTLEMENT_QUEUES.BTC
    case 'SOLANA':
      return SETTLEMENT_QUEUES.SOLANA
    default:
      throw new Error(`Unsupported network type: ${networkType}`)
  }
}

async function signalPayment(req, res) {
  try {
    const { trade_id, total_fee_amount, trade_deadline, script_deadline } = req.body

    // Fetch the trade from the database
    const trade = await tradeRepository.findById(trade_id)
    if (!trade) {
      return res.status(400).json({
        trade_id,
        status: 'error',
        error: 'Trade not found',
      })
    }

    // Validate trade status - must be SELECTED to proceed
    if (trade.status !== TradeStatus.SELECTED) {
      return res.status(400).json({
        trade_id,
        status: 'error',
        error: `Invalid trade status: ${trade.status}`,
      })
    }

    // Get the destination token to determine which queue to use
    const toToken = await tokenService.getTokenByTokenId(trade.toTokenId)
    const queueName = getQueueNameByNetworkType(toToken.networkType)

    // Queue the payment task to the appropriate network queue
    await paymentQueue.add(queueName, {
      tradeId: trade_id,
    })

    // Update trade status to SETTLING
    await tradeRepository.updateStatus(trade_id, TradeStatus.SETTLING)

    return res.status(200).json({
      trade_id,
      status: 'acknowledged',
      error: '',
    })
  } catch (error) {
    return res.status(500).json({
      trade_id: req.body.trade_id || '',
      status: 'error',
      error: error.message,
    })
  }
}

4. Solver API Endpoints for PMMs

These API endpoints are provided by the Solver backend for PMMs to retrieve token information and submit settlement data.

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.

4.1. Endpoint: /v1/market-maker/tokens

Description

Returns a list of tokens supported by the Solver Backend.

Request Parameters

  • HTTP Method: GET

Example Request

GET /v1/market-maker/tokens

Expected Response

  • HTTP Status: 200 OK
  • Response Body: JSON containing supported networks, tokens, and trading pairs
{
  "data": {
    "supported_networks": [
      {
        "network_id": "bitcoin_testnet",
        "name": "Bitcoin Testnet",
        "symbol": "tBTC",
        "type": "BTC",
        "logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg"
      },
      {
        "network_id": "ethereum_sepolia",
        "name": "Ethereum Sepolia",
        "symbol": "ETH",
        "type": "EVM",
        "logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg"
      }
    ],
    "tokens": [
      {
        "id": 2,
        "network_id": "bitcoin_testnet",
        "token_id": "tBTC",
        "network_name": "Bitcoin Testnet",
        "network_symbol": "tBTC",
        "network_type": "BTC",
        "token_name": "Bitcoin Testnet",
        "token_symbol": "tBTC",
        "token_address": "native",
        "token_decimals": 8,
        "token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/tbtc.svg",
        "network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/btc_network.svg",
        "active": true,
        "created_at": "2024-10-28T07:24:33.179Z",
        "updated_at": "2024-11-07T04:40:46.454Z"
      },
      {
        "id": 11,
        "network_id": "ethereum_sepolia",
        "token_id": "ETH",
        "network_name": "Ethereum Sepolia",
        "network_symbol": "ETH",
        "network_type": "EVM",
        "token_name": "Ethereum Sepolia",
        "token_symbol": "ETH",
        "token_address": "native",
        "token_decimals": 18,
        "token_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth.svg",
        "network_logo_uri": "https://storage.googleapis.com/Optimex-static-35291d79/images/tokens/eth_network.svg",
        "active": true,
        "created_at": "2024-11-22T08:36:59.175Z",
        "updated_at": "2024-11-22T08:36:59.175Z"
      }
    ],
    "pairs": [
      {
        "from_token_id": "ETH",
        "to_token_id": "tBTC",
        "is_active": true
      },
      {
        "from_token_id": "tBTC",
        "to_token_id": "ETH",
        "is_active": true
      }
    ]
  }
}

4.2. Endpoint: /v1/market-maker/submit-settlement-tx

Description

Allows the PMM to submit settlement transaction hashes for trades. This endpoint is essential for completing the trade settlement process and must be called after making payments.

Request Parameters

  • HTTP Method: POST
  • Request Body (JSON):
{
  "trade_ids": ["0xTradeID1", "0xTradeID2", "..."],
  "pmm_id": "pmm001",
  "settlement_tx": "SettlementTransactionData",
  "signature": "0xSignatureData",
  "start_index": 0,
  "signed_at": 1719158400
}
  • trade_ids (array of strings): Array of trade IDs included in this settlement transaction.

  • pmm_id (string): Your PMM identifier, which must match what was used in the commitment phase.

  • signature (string): Your cryptographic signature for this submission.

  • start_index (integer): Starting position within batch settlements (typically 0 for single trades).

  • signed_at (integer): UNIX timestamp (seconds) when you signed this submission.

  • settlement_tx (string): Should be hex format with a 0x prefix

    • For EVM Chains:

    • For Bitcoin or Solana:

      • Must encode raw_tx string using the l2Encode function
      • Example raw_tx string: 3d83c7846d6e5b04279175a9592705a15373f3029b866d5224cc0744489fe403
      • After encoding
        "settlement_tx": "0x33643833633738343664366535623034323739313735613935393237303561313533373366333032396238363664353232346363303734343438396665343033"
import { ethers, toUtf8Bytes, toUtf8String } from 'ethers'

export const l2Encode = (info: string) => {
  // Helper function to ensure hex prefix
  const ensureHexPrefix = (value: string) => {
    return value.startsWith('0x') ? value : `0x${value}`
  }

  if (/^0x[0-9a-fA-F]*$/.test(info)) {
    return info
  }
  return ensureHexPrefix(ethers.hexlify(toUtf8Bytes(info)))
}

Example Request

POST /v1/market-maker/submit-settlement-tx
Content-Type: application/json

{
  "trade_ids": ["0xabcdef123456...", "0x123456abcdef..."],
  "pmm_id": "pmm001",
  "settlement_tx": "0x33643833633738343664366535623034323739313735613935393237303561313533373366333032396238363664353232346363303734343438396665343033",
  "signature": "0xSignatureData",
  "start_index": 0,
  "signed_at": 1719158400
}

Expected Response

  • HTTP Status: 200 OK
  • Response Body (JSON):
{
  "message": "Settlement transaction submitted successfully"
}

Notes

  • Trade IDs: Provide all trade IDs included in the settlement transaction.
  • Start Index: Used when submitting a batch of settlements to indicate the position within the batch.
  • Signature: Must be valid and verifiable by the solver backend.

4.3. Endpoint: /v1/market-maker/trades/:tradeId

Description

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.

Request Parameters

  • HTTP Method: GET
  • Path Parameters:
    • tradeId (string): The unique identifier for the trade to retrieve.

Example Request

GET /v1/market-maker/trades/0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9c7e51b670086c2

Expected Response

  • HTTP Status: 200 OK
  • Response Body: JSON containing detailed trade information.
{
  "code": 0,
  "message": "",
  "data": {
    "trade_id": "0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9c7e51b670086c2",
    "session_id": "0xa5c2aa8dbff701e1a05707212ce3fb824a6ddd970e5dff5e340d7422ce6bcd97",
    "solver_address": "0xe291307c85f8f0c710180fea7cca25108782dee1",
    "from_token": {
      "token_id": "ETH",
      "chain": "ethereum",
      "address": "native",
      "fee_in": true,
      "fee_out": true
    },
    "to_token": {
      "token_id": "BTC",
      "chain": "bitcoin",
      "address": "native",
      "fee_in": false,
      "fee_out": false
    },
    "amount_before_fees": "3250849775444909",
    "amount_after_fees": "3244348075894020",
    "from_user_address": "0x2997cb0850a0c92db99e6e8745ac83bfb93c10ac",
    "user_receiving_address": "bc1p68q6hew27ljf4ghvlnwqz0fq32qg7tsgc7jr5levfy8r74p5k52qqphk07",
    "script_timeout": 1745544704,
    "protocol_fee_in_bps": "20",
    "affiliate_fee_in_bps": "0",
    "total_fee": "6501699550889",
    "protocol_fee": "6501699550889",
    "affiliate_fee": "0",
    "mpc_asset_chain_pubkey": "0x03c36bcf548094cfc74ec1ea89fc5fe0304461653813cdaa98bc26e2d5221eba9b",
    "best_indicative_quote": "4404",
    "display_indicative_quote": "4404",
    "pmm_finalists": [
      {
        "pmm_id": "pmm_test",
        "pmm_receiving_address": "0xtestaddress"
      }
    ],
    "settlement_quote": "5014",
    "receiving_amount": "5014",
    "selected_pmm": "kypseli",
    "selected_pmm_receiving_address": "0xbee0225697a311af58096ce2f03a2b65f1702f00",
    "selected_pmm_operator": "0x01c4f660ccdc4e5bdc5ee477ab0016dc424c473a",
    "selected_pmm_sig_deadline": 1745472704,
    "commitment_retries": 1,
    "pmm_failure_stats": {},
    "commited_signature": "0x842f32d384e6627755bdaa9285727c09731ed44e92555555c7d211fb3333a4c970b8a717ac79560be35fb2f22dc3fb2d80443e88234605fd353c12011fb8d8851c",
    "min_amount_out": null,
    "trade_timeout": 1745472704,
    "user_deposit_tx": "0x202186375a3b8d55de4d8d1afb7f6a5bec8978cef3b705e6cb379729d03b16c7",
    "deposit_vault": "0xf7fedf4a250157010807e6ea60258e3b768149ff",
    "payment_bundle": {
      "trade_ids": ["0xfc24b9bc1299b50896027cb4c85d041c911e062147ffaf7ae9c7e51b670086c2"],
      "settlement_tx": "3d83c7846d6e5b04279175a9592705a15373f3029b866d5224cc0744489fe403",
      "signature": "0x479a5a89e7a871026b60307351ea650fc667890b25d3d02df7ed2e93f94db90d7c3f8dbd823220896b8ad49b13a90851199236e82a644ffbe99e53503929fe151b",
      "start_index": 0,
      "pmm_id": "kypseli",
      "signed_at": 1745459448
    },
    "user_signature": "0xfe4d3288db2b7d6ebc273dad1e1c55ecf9af2991fb89cc3e52fc0956c13746a043195cc22ed3c38bfa67c81e7819b53095b4282c5ee1d0c23a955baa38d754821b",
    "trade_submission_tx": "0x38dfc953a9d08d95d7218e993302f81180c4d1a9c85f84836f005770167b0133",
    "trade_select_pmm_tx": "0xc68dbf08e5774edd87ae78076ae498ebc4e489ae905f34b13682198f6dbcc6c0",
    "trade_make_payment_tx": "0x962a1d6cced99b1fa53450c50cf4f95cbf600dd25dcd145a98311d275ef22a38",
    "state": "Done",
    "last_update_msg": "Done. Changed at version 10",
    "version": 10
  }
}

5. PMM Making Payment

5.1. EVM

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.

const { ethers } = require('ethers')

async function makeEVMPayment(tradeId, toAddress, amount, token, protocolFeeAmount) {
  try {
    // Get the private key from your secure storage
    const privateKey = process.env.PMM_EVM_PRIVATE_KEY

    // Set up the provider and signer
    const rpcUrl = getRpcUrlForNetwork(token.networkId)
    const provider = new ethers.JsonRpcProvider(rpcUrl)
    const signer = new ethers.Wallet(privateKey, provider)

    // Get the payment contract address
    const paymentAddress = getPaymentAddressForNetwork(token.networkId)

    // Create the contract instance
    const paymentAbi = [
      // ABI for the payment contract
      'function payment(bytes32 tradeId, address token, address recipient, uint256 amount, uint256 feeAmount, uint256 deadline) payable returns (bool)',
    ]
    const paymentContract = new ethers.Contract(paymentAddress, paymentAbi, signer)

    // Calculate the deadline (30 minutes from now)
    const deadline = Math.floor(Date.now() / 1000) + 30 * 60

    // If the token is native, we need to set the value
    const value = token.tokenAddress === 'native' ? amount : 0
    const tokenAddress = token.tokenAddress === 'native' ? ethers.ZeroAddress : token.tokenAddress

    // Submit the transaction
    const tx = await paymentContract.payment(tradeId, tokenAddress, toAddress, amount, protocolFeeAmount, deadline, {
      value,
    })

    console.log(`Transfer transaction sent: ${tx.hash}`)

    // Return the transaction hash with the 0x prefix
    return `0x${tx.hash.replace(/^0x/, '')}`
  } catch (error) {
    console.error('EVM payment error:', error)
    throw error
  }
}

5.2. Bitcoin

In case the target chain is Bitcoin, the transaction should have at least N + 1 outputs, with the first N outputs being the settlement UTXOs for trades, and one of them being the change UTXO for the user with the correct amount. The output N + 1 is the OP_RETURN output with the hash of tradeIds.

import { getTradeIdsHash } from '@optimex-xyz/market-maker-sdk'

import axios from 'axios'
import * as bitcoin from 'bitcoinjs-lib'
import { ECPairFactory } from 'ecpair'
import * as ecc from 'tiny-secp256k1'

async function makeBitcoinPayment(params) {
  const { toAddress, amount, token, tradeId } = params
  const ECPair = ECPairFactory(ecc)

  // Set up Bitcoin library
  bitcoin.initEccLib(ecc)

  // Get network configuration
  const network = getNetwork(token.networkId)
  const rpcUrl = getRpcUrl(token.networkId)

  // Create keypair from private key
  const keyPair = ECPair.fromWIF(process.env.PMM_BTC_PRIVATE_KEY, network)
  const payment = bitcoin.payments.p2tr({
    internalPubkey: Buffer.from(keyPair.publicKey.slice(1, 33)),
    network,
  })

  if (!payment.address) {
    throw new Error('Could not generate address')
  }

  // Get UTXOs for the address
  const utxos = await getUTXOs(payment.address, rpcUrl)
  if (utxos.length === 0) {
    throw new Error(`No UTXOs found in ${token.networkSymbol} wallet`)
  }

  // Create and sign transaction
  const psbt = new bitcoin.Psbt({ network })
  let totalInput = 0n

  // Add inputs
  for (const utxo of utxos) {
    if (!payment.output) {
      throw new Error('Could not generate output script')
    }

    const internalKey = Buffer.from(keyPair.publicKey.slice(1, 33))

    psbt.addInput({
      hash: utxo.txid,
      index: utxo.vout,
      witnessUtxo: {
        script: payment.output,
        value: BigInt(utxo.value),
      },
      tapInternalKey: internalKey,
    })

    totalInput += BigInt(utxo.value)
  }

  // Check if we have enough balance
  if (totalInput < amount) {
    throw new Error(`Insufficient balance. Need ${amount} satoshis, but only have ${totalInput} satoshis`)
  }

  // Get fee rate
  const feeRate = await getFeeRate(rpcUrl)
  const fee = BigInt(Math.ceil(200 * feeRate))
  const changeAmount = totalInput - amount - fee

  // Add recipient output
  psbt.addOutput({
    address: toAddress,
    value: amount,
  })

  // Add change output if needed
  if (changeAmount > 546n) {
    psbt.addOutput({
      address: payment.address,
      value: changeAmount,
    })
  }

  // Add OP_RETURN output with trade ID hash
  const tradeIdsHash = getTradeIdsHash([tradeId])
  psbt.addOutput({
    script: bitcoin.script.compile([bitcoin.opcodes.OP_RETURN, Buffer.from(tradeIdsHash.slice(2), 'hex')]),
    value: 0n,
  })

  // Sign inputs
  const toXOnly = (pubKey) => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33))
  const tweakedSigner = keyPair.tweak(bitcoin.crypto.taggedHash('TapTweak', toXOnly(keyPair.publicKey)))

  for (let i = 0; i < psbt.data.inputs.length; i++) {
    psbt.signInput(i, tweakedSigner, [bitcoin.Transaction.SIGHASH_DEFAULT])
  }

  psbt.finalizeAllInputs()

  // Extract transaction
  const tx = psbt.extractTransaction()
  const rawTx = tx.toHex()

  // Broadcast transaction
  const response = await axios.post(`${rpcUrl}/api/tx`, rawTx, {
    headers: {
      'Content-Type': 'text/plain',
    },
  })

  return response.data // Transaction ID
}