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).
  • affiliateId 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.
  • affiliateId 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 affiliate during registration.
  • affiliateId: 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": "AFFILIATE_NOT_FOUND",
    "message": "Affiliate does not exist",
    "details": {}
  }
}

Common error codes:

  • INVALID_ARGUMENT
  • AFFILIATE_ALREADY_EXISTS
  • AFFILIATE_NOT_FOUND
  • SWAP_NOT_FOUND
  • RATE_LIMITED
  • INTERNAL_ERROR

6) REST endpoints

6.1 Register affiliate

  • Method/Path: POST /affiliates
  • Purpose: register new affiliate by evmWallet and generate affiliateId.

Request body:

{
  "evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}

Example to register a new affiliate by EVM wallet:

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

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

Response 201:

{
  "affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}

Possible errors:

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

6.2 Get affiliate by wallet

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

Example to resolve affiliateId by partner wallet:

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

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

Response 200:

{
  "affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}

Possible errors:

  • 404 AFFILIATE_NOT_FOUND

6.3 Get fee balance

  • Method/Path: GET /affiliates/{affiliateId}/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"
AFFILIATE_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"

curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/fee-balance"

Response 200:

{
  "balanceUsd": "1234.560000"
}

Possible errors:

  • 404 AFFILIATE_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 on https://api.pactswap.io
  • Purpose: retrieve multiple swap quotes for a given amountFrom (compare rates from different routes).

Query params:

  • from (string, required) — L2 C1 contract id for the source token.
  • to (string, required) — L2 C2 contract id for the destination token.
  • amountFrom (number, required) — amount in smallest units.
  • fromType (string, optional) — source chain/type (for example, eth, btc, bnb).
  • toType (string, optional) — destination chain/type.
  • affiliates (array, optional) — affiliate fee recipients. In query strings use indexed fields, for example affiliates[0][affiliateId] and affiliates[0][bips]. Use your affiliateId from registration.
  • slippageBips (number, optional) — slippage tolerance in basis points (1 bp = 0.01%, so 100 = 1%). Minimum 50 when provided. Quotes include minAmountTo derived from this.

Contract ids and presets are deployment-specific. Replace the placeholders below with values from your environment before calling production.

Example: request quotes to swap 1 ETH to BNB:

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

# ETH (source): C1 base + destination C2 maker for BNB — replace with your deployment ids
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"

curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
  --get \
  --data-urlencode "from=$FROM_C1" \
  --data-urlencode "to=$TO_C2" \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode "affiliates[0][affiliateId]=0x9a8b7c6d5e4f3210abcdeffedcba012345678901" \
  --data-urlencode "affiliates[0][bips]=25" \
  --data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
  --data-urlencode "affiliates[1][bips]=40"

-g tells curl not to treat [ / ] in affiliates[0][...] as URL glob characters.

Response 200 (example):

[
  {
    "amountTo": 50000000,
    "minAmountTo": 49500000,
    "route": "0xC0ffee254729296a45a3885639AC7E10F9d54979",
    "lp": "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.
  • route / lp — liquidity provider identifier from the quote (hex without 0x prefix). Pass it to compose as route or lp.
  • minAmountTo — lower bound on the destination amount implied by the quoted amountTo and your slippageBips (same smallest units as amountTo).
  • estimatedSwapTimeSeconds — 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 on https://api.pactswap.io
  • Purpose: compose an unsigned L1 transaction based on amountFrom. The affiliate/user must sign this transaction with their wallet, then broadcast it to the source chain.

Query params (required for EVM swaps):

  • fromType, toType (string)
  • fromWalletAddress, toWalletAddress (string) — valid addresses for the respective chains
  • fromC1ContractId, fromC2ContractId, toC1ContractId, toC2ContractId (string) — L2 contract ids for source/destination tokens
  • amountFrom (number)
  • preset (number) — swap routing preset for the destination chain and address format (deployment-specific)
  • route or lp (string) — liquidity provider id from the quote response (hex without 0x)

Query params (optional):

  • affiliates (array) — same indexed query format as in § 6.5
  • slippageBips (number, optional) — same meaning as for quotes (≥ 50 when provided); see § 6.5

Query params (UTXO-based source chains only):

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

Example: compose an unsigned EVM transaction to swap 1 ETH to BNB (use route from § 6.5):

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

FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
FROM_C2="0xe2dcfe88387f5dbc8ec583727cf4280462ceb356be3199d320ed818089f8a693"
TO_C1="0x6f20d1802288c90d2a044d3f7afae73fda261b27d85c0e01fdaaf3199b2c0e85"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
ROUTE="0xC0ffee254729296a45a3885639AC7E10F9d54979" # from quotes[].route or quotes[].lp
PRESET="9007199254740257" # deployment-specific; depends on toType + toWalletAddress format

curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "fromWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
  --data-urlencode "toWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "fromC1ContractId=$FROM_C1" \
  --data-urlencode "fromC2ContractId=$FROM_C2" \
  --data-urlencode "toC1ContractId=$TO_C1" \
  --data-urlencode "toC2ContractId=$TO_C2" \
  --data-urlencode "preset=$PRESET" \
  --data-urlencode "route=$ROUTE" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode "affiliates[0][affiliateId]=0x9a8b7c6d5e4f3210abcdeffedcba012345678901" \
  --data-urlencode "affiliates[0][bips]=25" \
  --data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
  --data-urlencode "affiliates[1][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 affiliate/user wallet flow (outside this API endpoint).

7) TypeScript interfaces for affiliate clients

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

export interface RegisterAffiliateRequest {
  evmWallet: AddressLike;
}

export interface RegisterAffiliateResponse {
  affiliateId: AddressLike;
}

export interface GetAffiliateResponse {
  affiliateId: 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 /affiliates with partner evmWallet.
BASE_URL="https://api-affiliate.pactswap.io/api/v1"

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

curl -sS "$BASE_URL/swaps/$L1TXID"
  1. Get swap quotes on Pact Swap API (pick route / lp from the response):
    • GET https://api.pactswap.io/pactswap_cm/getSwapQuotesByAmountFrom
    • pass your affiliateId from step 2 in affiliates; set slippageBips for minAmountTo on quotes
PACTSWAP_BASE_URL="https://api.pactswap.io"
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"

# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
  --get \
  --data-urlencode "from=$FROM_C1" \
  --data-urlencode "to=$TO_C2" \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode "affiliates[0][affiliateId]=$AFFILIATE_ID" \
  --data-urlencode "affiliates[0][bips]=25" \
  --data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
  --data-urlencode "affiliates[1][bips]=40"
  1. Compose swap tx by selected route on Pact Swap API:
    • GET https://api.pactswap.io/pactswap_cm/composeSwapTxByAmountFrom
    • pass route (or lp) from step 5, all four contract ids, preset, affiliates, and slippageBips (same values as step 5)
    • endpoint returns an unsigned tx payload; sign it in wallet flow and then broadcast
PACTSWAP_BASE_URL="https://api.pactswap.io"
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
FROM_C2="0xe2dcfe88387f5dbc8ec583727cf4280462ceb356be3199d320ed818089f8a693"
TO_C1="0x6f20d1802288c90d2a044d3f7afae73fda261b27d85c0e01fdaaf3199b2c0e85"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
ROUTE="0xC0ffee254729296a45a3885639AC7E10F9d54979"
PRESET="9007199254740257"

curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
  --get \
  --data-urlencode "fromType=eth" \
  --data-urlencode "toType=bnb" \
  --data-urlencode "fromWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
  --data-urlencode "toWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
  --data-urlencode "amountFrom=1000000000000000000" \
  --data-urlencode "fromC1ContractId=$FROM_C1" \
  --data-urlencode "fromC2ContractId=$FROM_C2" \
  --data-urlencode "toC1ContractId=$TO_C1" \
  --data-urlencode "toC2ContractId=$TO_C2" \
  --data-urlencode "preset=$PRESET" \
  --data-urlencode "route=$ROUTE" \
  --data-urlencode "slippageBips=100" \
  --data-urlencode "affiliates[0][affiliateId]=$AFFILIATE_ID" \
  --data-urlencode "affiliates[0][bips]=25" \
  --data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
  --data-urlencode "affiliates[1][bips]=40"

Operational recommendations:

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

9) Operational guidance for affiliates

9.1 Idempotency and retries

  • For POST /affiliates, 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 (affiliateId 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 affiliateId 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?