Skip to content

Repository files navigation

OKX Intent Swap SDK

TypeScript SDK for building Settlement.settle() calldata — converts Solver API /solve request + response into ABI-encoded calldata ready for on-chain submission.

Quick Start (Standalone)

No npm install needed. Copy the standalone example directly into your project:

examples/settle-calldata-standalone.ts

This single file contains all types, ABI, and the buildSettleCalldata() function — zero internal dependencies, only requires ethers@^5.7.0.

# 1. Copy the standalone file into your project
curl -o settle-calldata.ts https://raw.githubusercontent.com/okxlabs/web3-dex-evm-intent-sdk/main/examples/settle-calldata-standalone.ts

# 2. Import and use
import { buildSettleCalldata } from './settle-calldata';
import { ethers } from 'ethers';

// Step 1: Receive /solve request & compute your solution (response)
const request = await receiveAuctionRequest();   // SolveRequest
const response = await computeSolution(request); // SolveResponse

// Step 2: Receive settleId from /settle callback
const settleId = settleRequest.settleInfos[0].settleId;

// Step 3: Build threshold-schema calldata
// The standalone helper takes interactions directly as its fourth argument.
const { calldata } = buildSettleCalldata(request, response, settleId, [[], [], []]);

// Step 4: Submit settle() transaction
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const solver = new ethers.Wallet(SOLVER_PK, provider);
const tx = await solver.sendTransaction({
  to: SETTLEMENT_CONTRACT,
  data: calldata,
});
await tx.wait();

The standalone file demonstrates only the threshold schema. Solvers that must serve both legacy and threshold contracts should use the address-aware package API described below.

Source Packages (monorepo internal, not published to npm)

Directory Description
packages/solver Solver calldata builder (buildSettleCalldata)
packages/common Core types, constants, and utilities
packages/contracts Settlement contract ABI

API Types: SolveRequest / SolveResponse

The SDK defines strict types for the /solve endpoint wire format.

SolveRequest

interface SolveRequest {
  auctionId?: string;            // Auction ID from /solve API (not used by buildSettleCalldata)
  orders: SolveRequestOrder[];
}

interface SolveRequestOrder {
  fromTokenAddress: string;      // Token to sell
  toTokenAddress: string;        // Token to buy
  owner: string;                 // Order signer address
  receiver: string;              // Proceeds recipient (owner if same)
  fromTokenAmount: string;       // Sell amount (decimal string)
  toTokenAmount: string;         // Min buy amount (decimal string)
  validTo: number;               // Expiry (unix seconds)
  appDataHash: string;           // Application data (bytes32 hex)
  swapMode: string;              // "exactIn" | "exactOut"
  partiallyFillable: boolean;
  signingScheme: string;         // "eip712" | "ethSign" | "eip1271" | "preSign"
  signature: string;             // Order signature (hex)
  thresholdPercent?: string;     // Output shortfall tolerance, 1e9 precision (default "0")
  commissionInfos: ApiCommissionInfo[];
}

thresholdPercent is settlement-only metadata supplied by the backend and is not part of the user's EIP-712 signature. Values must be in [0, 1_000_000_000]; for example, 5_000_000 means 50 bps (0.5%). For a threshold Settlement, omitting it encodes 0, preserving the previous zero-tolerance behavior. A legacy Trade has no thresholdPercent tuple field at all. The signed toTokenAmount remains the hard minimum payout floor.

Deployment note: the threshold contract has a new settle selector (0x462c3ed1) and must be called at the newly deployed Settlement address. That address is also the EIP-712 verifyingContract, so orders signed for the previous Settlement address cannot be reused.

SolveResponse

interface SolveResponse {
  solutions: Solution[];         // Typically one solution
}

interface Solution {
  clearingPrices: Record<string, string>;  // tokenAddress → price (decimal string)
  orders: SolveResponseOrder[];            // Parallel to request.orders
  surplusFeeInfo: ApiSurplusFeeInfo;       // Protocol surplus fee config
}

interface SolveResponseOrder {
  executedFromTokenAmount: string;  // Actual sell amount (decimal string)
  executedToTokenAmount: string;    // Actual buy amount (decimal string)
  commissionInfos: ApiCommissionInfo[];
  solverFeeInfo: ApiSolverFeeInfo;
}

Fee Types

interface ApiCommissionInfo {
  feePercent: string;              // Rate in 1e9 precision ("3000000" = 0.3%)
  referrerWalletAddress: string;   // Fee recipient
  feeDirection: boolean;           // true = fromToken, false = toToken
  toB: boolean;                    // true = Settlement pays, false = user pays
  commissionType: string;          // "okx" | "parent" | "child"
}

interface ApiSolverFeeInfo {
  feePercent: string;              // Solver fee rate (1e9 precision)
  solverAddress: string;
  feeDirection: boolean;           // true = fromToken, false = toToken
  feeAmount: string;               // Informational total amount
}

interface ApiSurplusFeeInfo {
  feePercent: string;              // Surplus fee rate (1e9 precision)
  trimReceiver: string;            // Protocol fee recipient
  flag: string;                    // Flag value
}

Note: The raw API response may be wrapped in { code, msg, data: { solutions } }. You need to unwrap it before passing to buildSettleCalldata(). See the full example for the unwrapping pattern.

Clearing Prices: Two Modes

buildSettleCalldata supports two strategies for computing clearing prices:

Mode 1: API Prices

Uses solution.clearingPrices from the API response directly. The SDK converts decimal string ratios into uint256 integers by scaling all prices to the same decimal precision.

const { calldata } = buildSettleCalldata(request, response, settleId, {
  useComputedPrices: false,
});

When to use: Your solver API already returns well-formed clearing prices.

Mode 2: Computed Prices (default)

Derives clearing prices from execution amounts and fees. For each order:

  • P_sell = executedToTokenAmount (buy-side price)
  • P_buy = executedFromTokenAmount - totalFromTokenFees (sell-side price minus fees)
const { calldata } = buildSettleCalldata(request, response, settleId, {
  useComputedPrices: true,
});

When to use: You want prices that exactly match the execution amounts, or when handling complex multi-token batches where API prices may not capture fee adjustments precisely.

buildSettleCalldata API

function buildSettleCalldata(
  request: SolveRequest,
  response: SolveResponse,
  settleId: string | bigint,       // from /settle callback's settleInfos[].settleId
  options?: BuildSettleCalldataOptions
): BuildSettleCalldataResult;

interface BuildSettleCalldataOptions {
  interactions?: [Interaction[], Interaction[], Interaction[]];  // [pre, swap, post]
  solutionIndex?: number;    // Which solution to use (default: 0)
  useComputedPrices?: boolean; // Derive prices from execution data (default: true)
  settlementAddress?: string; // Selects the calldata schema from the SDK's known-address map
}

Serving legacy production and threshold beta contracts

The same SDK supports both Settlement calldata schemas without exposing a version parameter. Pass the target Settlement address and the SDK selects its mapped schema. The address can come from the /solve request's settlementContract field or any solver-side configuration:

import { buildSettleCalldata } from '@okx-intent-swap/sdk-solver';

const result = buildSettleCalldata(request, response, settleId, {
  settlementAddress: rawSolveRequest.settlementContract,
});

For backwards compatibility with existing production solvers, omitting settlementAddress keeps the historical legacy calldata behavior. A supplied but unknown address throws instead of falling back to a default. Address resolution is local and does not perform an RPC request. Decoding automatically detects the calldata schema from the selector.

What buildSettleCalldata Does Internally

  1. Convert settleId → bigint (from /settle callback, distinct from auctionId)
  2. Collect all unique token addresses → tokens[]
  3. Convert clearing prices (decimal string → uint256)
  4. Build Trade[] by merging request orders + response execution data; include thresholdPercent only for the threshold schema
  5. Pack commission flags (direction + ToB + label → single uint256 bitmask)
  6. Sort trades by toTokenAddressIndex ascending (Settlement contract requirement)
  7. ABI-encode everything as Settlement.settle() calldata

Examples

Standalone (recommended for solvers)

examples/settle-calldata-standalone.ts — single-file, zero-dependency (except ethers). Copy it directly into your solver project.

The standalone file demonstrates the threshold schema. Solvers that serve both contract versions should use the address-aware API from packages/solver described above.

Monorepo example

examples/nodejs-ethers5/src/build-calldata.ts — demonstrates how to transform raw /solve API JSON into SDK types and produce ABI-encoded calldata.

cd examples/nodejs-ethers5
pnpm build && pnpm build-calldata

Development

pnpm install       # Install dependencies
pnpm build         # Build all packages
pnpm test          # Run tests
pnpm typecheck     # Type check

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages