Pact Swap

Affiliate Service API Integration Guide

NOTE: API documentation is currently under development


1) Scope

AffiliateService - service for affiliate integration.

This API is dedicated to affiliate partners and designed for production integrations.

Key points:

  • API is public/open (no auth headers required).
  • integratorId is generated by the service. It may look like an EVM wallet address, but is not the same value as the original wallet used during registration.
  • integratorId is the primary identifier for all subsequent calls after registration.
  • When fee balance reaches 100 USD, the system automatically creates a withdrawal to the EVM wallet specified during registration.

2) Core terms

  • evmWallet: external EVM wallet provided by integrator during registration.
  • integratorId: service-generated affiliate identifier (external-facing ID for API calls).
  • l1txid: swap transaction identifier on L1 side.

3) Data format and transport conventions

  • Base URL (example): https://api-affiliate.pactswap.io/api/v1
  • Content type: application/json
  • Successful responses use 2xx status codes.
  • Error responses use 4xx/5xx status codes and unified error envelope.
  • Address/id fields are case-insensitive in requests, but response values should be treated as canonical.
  • Monetary values are returned as string decimals to avoid precision loss.
  • For swap endpoints, numeric token amounts (for example, amountFrom) are in the smallest token units (not human-readable units): satoshis for BTC, wei for ETH/BNB/POL, etc. Example: amountFrom=100 for BTC means 100 satoshis, not 100 BTC.

4) Versioning policy

  • API version is part of URL: /api/v1.
  • Backward-compatible additions may include new optional fields.
  • Breaking changes are released under a new version path (for example, /api/v2).

5) Error response format

Unified error envelope (recommended):

{
  "error": {
    "code": "INTEGRATOR_NOT_FOUND",
    "message": "Integrator does not exist",
    "details": {}
  }
}

Common error codes:

  • INVALID_ARGUMENT
  • INTEGRATOR_ALREADY_EXISTS
  • INTEGRATOR_NOT_FOUND
  • SWAP_NOT_FOUND
  • RATE_LIMITED
  • INTERNAL_ERROR

6) REST endpoints

6.1 Register integrator

  • Method/Path: POST /integrators
  • Purpose: register new integrator by evmWallet and generate integratorId.

Request body:

{
  "evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}

Example to register a new integrator by EVM wallet:

BASE_URL="https://api-affiliate.pactswap.io/api/v1"

curl -sS -X POST "$BASE_URL/integrators" \
  -H "Content-Type: application/json" \
  -d '{
    "evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
  }'

Response 201:

{
  "integratorId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}

Possible errors:

  • 400 INVALID_ARGUMENT (invalid wallet format)
  • 409 INTEGRATOR_ALREADY_EXISTS (wallet already registered)

6.2 Get integrator by wallet

  • Method/Path: GET /integrators/by-wallet/{evmWallet}
  • Purpose: resolve existing integratorId by source EVM wallet.

Example to resolve integratorId by partner wallet:

BASE_URL="https://api-affiliate.pactswap.io/api/v1"
EVM_WALLET="0x1234567890abcdef1234567890abcdef12345678"

curl -sS "$BASE_URL/integrators/by-wallet/$EVM_WALLET"

Response 200:

{
  "integratorId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}

Possible errors:

  • 404 INTEGRATOR_NOT_FOUND

6.3 Get fee balance

  • Method/Path: GET /integrators/{integratorId}/fee-balance
  • Purpose: get current fee balance in USD.

Example to get current affiliate fee balance:

BASE_URL="https://api-affiliate.pactswap.io/api/v1"
INTEGRATOR_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"

curl -sS "$BASE_URL/integrators/$INTEGRATOR_ID/fee-balance"

Response 200:

{
  "balanceUsd": "1234.560000"
}

Possible errors:

  • 404 INTEGRATOR_NOT_FOUND

6.4 Get swap info

  • Method/Path: GET /swaps/{l1txid}

Example to get swap info by L1 transaction id:

BASE_URL="https://api-affiliate.pactswap.io/api/v1"
L1TXID="0xaaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"

curl -sS "$BASE_URL/swaps/$L1TXID"

Response 200 (normal case):

{
  "kind": "normal",
  "feesAcquiredUsdt": "245.100000",
  "feeBips": "30"
}

Response 200 (reversed pact case):

{
  "kind": "reversed_pact"
}

Possible errors:

  • 404 SWAP_NOT_FOUND

6.5 Get swap quotes by send amount (quotes)

  • Method/Path: GET /pactswap_cm/getSwapQuotesByAmountFrom
  • Purpose: retrieve multiple swap quotes for a given amountFrom (compare rates from different route).

Query params:

  • amountFrom (number, required) โ€” amount in smallest units
  • fromType (string, required) โ€” source chain/type
  • toType (string, required) โ€” destination chain/type
  • affiliates (string, optional) โ€” JSON array of affiliate fee recipients: [{ "affiliateId": string, "bips": number }, ...]. Use your integratorId from registration as affiliateId.
  • slippageBips (number, optional) โ€” slippage tolerance: maximum allowed deviation from the expected receive amount, in basis points (1 bp = 0.01%, so e.g. 100 โ‰ˆ 1%). If sent, the API enforces a minimum of 50 (0.5%). Quotes include minAmountTo derived from this (see response example).

Example how to request a Quote to swap 1 ETH to BNB:

BASE_URL="https://api.pactswap.io"

curl -sS "$BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode 'affiliates=[{"affiliateId":"0x9a8b7c6d5e4f3210abcdeffedcba012345678901","bips":25},{"affiliateId":"0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12","bips":40}]'

Response 200 (example):

[
  {
    "amountTo": 50000000,
    "minAmountTo": 49500000,
    "route": "0xC0ffee254729296a45a3885639AC7E10F9d54979",
    "estimatedSwapTimeSeconds": 120
  }
]

Notes:

  • amountFrom is always provided in the smallest units of the source token (for example, satoshis for BTC, wei for ETH/BNB/POL).
  • Example: amountFrom=100 for BTC means 100 satoshis, not 100 BTC.
  • minAmountTo โ€” lower bound on the destination amount implied by the quoted amountTo and your slippageBips (same smallest units as amountTo).
  • estimatedSwapTimeSeconds โ€” next to the liquidity provider id in each quote, approximate expected swap duration in seconds (not a hard guarantee).

6.6 Compose swap transaction by send amount (compose)

  • Method/Path: GET /pactswap_cm/composeSwapTxByAmountFrom
  • Purpose: compose an unsigned L1 transaction based on amountFrom. The integrator/user must sign this transaction with their wallet, then broadcast it to the source chain.

Query params (most common):

  • fromType (string, required)
  • toType (string, required)
  • fromWalletAddress (string, required)
  • toWalletAddress (string, required)
  • amountFrom (number, required)
  • route (string, required)
  • affiliates (string, optional) โ€” JSON array of affiliate fee recipients: [{ "affiliateId": string, "bips": number }, ...]. Use your integratorId from registration as affiliateId.
  • slippageBips (number, optional) โ€” same meaning as for quotes: slippage in basis points (โ‰ฅ 50 when provided); see ยง 6.5.

Query params (UTXO-based chains only):

  • fromPublicKey (string, required for BTC/LTC/DOGE)
  • utxos (string, required for BTC/LTC/DOGE) โ€” JSON string with UTXOs

Example how to request an unsigned EVM transaction to swap from 1 ETH to BTC:

BASE_URL="https://api.pactswap.io"

curl -sS "$BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=btc" \
  --data-urlencode "fromWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
  --data-urlencode "toWalletAddress=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "route=0xC0ffee254729296a45a3885639AC7E10F9d54979" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode 'affiliates=[{"affiliateId":"0x9a8b7c6d5e4f3210abcdeffedcba012345678901","bips":25},{"affiliateId":"0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12","bips":40}]'

Response 200 (example):

{
  "rawTx": "0x123..."
}

Notes:

  • rawTx is an unsigned transaction payload returned by the API for client-side signing.
  • Signing and broadcasting are performed by the integrator/user wallet flow (outside this API endpoint).

7) TypeScript interfaces for integrator clients

export type AddressLike = string;
export type DecimalString = string;

export interface RegisterIntegratorRequest {
  evmWallet: AddressLike;
}

export interface RegisterIntegratorResponse {
  integratorId: AddressLike;
}

export interface GetIntegratorResponse {
  integratorId: AddressLike;
}

export interface GetFeeBalanceResponse {
  balanceUsd: DecimalString;
}

export interface SwapInfoNormal {
  kind: "normal";
  feesAcquiredUsdt: DecimalString;
  feeBips: number;
}

export interface SwapInfoReversedPact {
  kind: "reversed_pact";
}

export type GetSwapInfoResponse = SwapInfoNormal | SwapInfoReversedPact;

export interface ApiErrorResponse {
  error: {
    code: string;
    message: string;
    details?: Record<string, unknown>;
  };
}
  1. Call POST /integrators with partner evmWallet.
BASE_URL="https://api-affiliate.pactswap.io/api/v1"

curl -sS -X POST "$BASE_URL/integrators" \
  -H "Content-Type: application/json" \
  -d '{
    "evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
  }'
  1. Store returned integratorId as primary identifier for all next API calls.
# Example of storing returned integratorId for next requests
INTEGRATOR_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
  1. For accounting UI, periodically call:
    • GET /integrators/{integratorId}/fee-balance
curl -sS "$BASE_URL/integrators/$INTEGRATOR_ID/fee-balance"
  1. For swap audit/reconciliation:
    • call GET /swaps/{l1txid}
L1TXID="0xaaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"

curl -sS "$BASE_URL/swaps/$L1TXID"
  1. Get swap quotes (pick ROUTE):
    • call GET /pactswap_cm/getSwapQuotesByAmountFrom
# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS "$BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "amountFrom=1000000000000000000"
  1. Compose swap tx by selected ROUTE:
    • call GET /pactswap_cm/composeSwapTxByAmountFrom (pass route)
    • endpoint returns an unsigned tx payload; sign it in wallet flow and then broadcast
ROUTE="020a89e0698443aaf4ecd613317d2fdf90ef73b99244182fc7bb3c6b85f375b876"

# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS "$BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "fromWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
  --data-urlencode "toWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "route=$ROUTE"

Operational recommendations:

  • Treat POST /integrators as idempotent by evmWallet on client side (retry-safe logic).
  • Use exponential backoff for temporary 5xx failures.

9) Operational guidance for integrators

9.1 Idempotency and retries

  • For POST /integrators, client should implement retry-safe behavior keyed by evmWallet.
  • Recommended retry strategy for transient failures (5xx, network timeout): exponential backoff with jitter.

9.2 Rate limits

  • Default partner limit recommendation: 60 requests/minute per IP.
  • If response is 429 RATE_LIMITED, retry after backoff.
  • Clients should cache stable lookup data (integratorId resolution) to reduce request volume.

9.3 Timeouts and polling

  • Recommended client timeout per request: 10s.
  • For operational dashboards, polling interval for balance/history: 15-60s depending on UI needs.

9.4 Production readiness checklist (partner side)

  • Store integratorId as primary partner key.
  • Handle both swap variants: normal and reversed_pact.
  • Implement structured handling for documented error codes.
  • Add observability for request id, latency, non-2xx responses, and retry attempts.

Was this documentation helpful? Any suggestions?