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.
  • After registration the affiliate is activated asynchronously (on-chain funding
    • fee-pool registration). Use isActivated from register/lookup responses before relying on fee balance or withdrawals.
  • Registration accepts evmWallet, btcWallet, or both. At least one is required.
  • Withdrawal mode is chosen at registration:
    • Omit withdrawalMode to keep the previous behavior: EVM-only affiliates withdraw automatically in usdt_bnb; any registration that includes btcWallet (Bitcoin-only or EVM + Bitcoin) is manual.
    • Pass withdrawalMode to override that default. auto also requires token. manual does not take token.
  • Automatic (auto): when the fee balance reaches the configured threshold (default 100 USD), the service withdraws in the token chosen at registration. btc pays btcWallet; every other token pays evmWallet. The token must match a wallet supplied in the same request.
  • Manual (manual): the partner calls POST /affiliates/{affiliateId}/withdraw-fees and chooses the payout token (usdt_bnb, usdc_bnb, usdt_eth, usdc_eth, eth, or btc) once the balance reaches the same threshold. The token must match a registered wallet.

2) Core terms

  • evmWallet: external EVM wallet. Optional when btcWallet is set. Used for every payout token except btc.
  • btcWallet: Bitcoin payout address (mainnet: legacy 1…/3… or Bech32 bc1q…/bc1p…). Optional when evmWallet is set. Used for btc payouts.
  • withdrawalMode: optional auto or manual. When omitted, EVM-only registration is auto with token usdt_bnb, and a Bitcoin address selects manual.
  • token: payout token for automatic withdrawal. Required only when withdrawalMode is auto. Must be btc when only btcWallet is set, and must not be btc when only evmWallet is set.
  • affiliateId: service-generated affiliate identifier (external-facing ID for API calls).
  • isActivated: whether on-chain activation finished successfully. Fee accrual and withdrawals require an activated affiliate.
  • 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 (except Base58 BTC addresses, which keep original casing).
  • 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
  • AFFILIATE_NOT_ACTIVATED
  • MANUAL_WITHDRAWAL_NOT_ALLOWED
  • TOKEN_WALLET_MISMATCH
  • BELOW_THRESHOLD
  • SWAP_NOT_FOUND
  • RATE_LIMITED
  • INTERNAL_ERROR

6) REST endpoints

6.1 Register affiliate

  • Method/Path: POST /affiliates
  • Purpose: register a new affiliate by evmWallet, btcWallet, or both, and generate affiliateId. Activation starts asynchronously; new affiliates return isActivated: false.

Request body (explicit mode):

{
  "evmWallet": "0x1234567890abcdef1234567890abcdef12345678",
  "btcWallet": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
  "withdrawalMode": "auto",
  "token": "btc"
}

withdrawalMode and token are optional. Existing clients can keep sending only wallets:

BodyResult
{ "evmWallet" }auto withdrawal in usdt_bnb (unchanged)
{ "evmWallet", "btcWallet" }manual withdrawal (unchanged)
{ "btcWallet" }manual withdrawal, Bitcoin-only
withdrawalMode: "auto" + tokenautomatic withdrawal in that token; the matching wallet must be in the body
withdrawalMode: "manual"manual withdrawal; do not send token

token values are the same as for withdraw-fees (usdt_bnb, usdc_bnb, usdt_eth, usdc_eth, eth, btc). btc requires btcWallet. Every other token requires evmWallet.

Example to register a new affiliate by EVM wallet (optional BTC):

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

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

Response 201:

{
  "affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901",
  "isActivated": false
}

Possible errors:

  • 400 INVALID_ARGUMENT (invalid wallet, missing wallet, token missing or not allowed for the mode, or token does not match the supplied wallet)
  • 409 AFFILIATE_ALREADY_EXISTS (EVM or BTC wallet already registered)

6.2 Get affiliate by wallet

  • Method/Path: GET /affiliates/by-wallet/{wallet}
  • Purpose: resolve existing affiliateId (and activation status) by the EVM or Bitcoin wallet used at registration. EVM lookup is unchanged; Bitcoin addresses are accepted as well (needed for Bitcoin-only affiliates).

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",
  "isActivated": true
}

Possible errors:

  • 400 INVALID_ARGUMENT (invalid wallet format)
  • 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:

  • 400 INVALID_ARGUMENT (invalid affiliateId format)
  • 404 AFFILIATE_NOT_FOUND

6.4 Manual fee withdrawal

  • Method/Path: POST /affiliates/{affiliateId}/withdraw-fees
  • Purpose: manually withdraw accumulated fees. Only available for affiliates registered in manual mode (the default when btcWallet is sent and withdrawalMode is omitted, or when withdrawalMode is manual). The affiliate must be activated, the fee balance must be at or above the withdrawal threshold, and token must match a wallet stored for that affiliate.

Request body:

{
  "token": "btc"
}

Supported token values:

tokenPayout chainDestination wallet
usdt_bnbBNBevmWallet
usdc_bnbBNBevmWallet
usdt_ethETHevmWallet
usdc_ethETHevmWallet
ethETHevmWallet
btcBTCbtcWallet

Example:

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

curl -sS -X POST "$BASE_URL/affiliates/$AFFILIATE_ID/withdraw-fees" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "btc"
  }'

Response 201 / 200:

{
  "recordId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "token": "btc",
  "feesUsdVirtual": 105,
  "l1Amount": "65000",
  "l1Address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
  "tokenDecimals": 8,
  "orderEmbedId": "embed-id-example",
  "orderL2Txid": "l2-txid-example"
}

Notes:

  • l1Amount is in the payout token’s smallest units (derived from fee USD notional and current token USD price).
  • orderEmbedId / orderL2Txid identify the on-chain payout order created by the service.

Possible errors:

  • 400 INVALID_ARGUMENT (invalid affiliateId / token)
  • 400 MANUAL_WITHDRAWAL_NOT_ALLOWED (affiliate is on automatic withdrawal)
  • 400 TOKEN_WALLET_MISMATCH (btc without btcWallet, or any other token without evmWallet)
  • 400 BELOW_THRESHOLD (details includes balanceUsd and thresholdUsd)
  • 404 AFFILIATE_NOT_FOUND
  • 422 AFFILIATE_NOT_ACTIVATED
  • 500 INTERNAL_ERROR

6.5 List affiliate withdrawals

  • Method/Path: GET /affiliates/{affiliateId}/withdrawals
  • Purpose: paginated list of fee withdrawals for the affiliate (automatic EVM payouts and manual withdraw-fees records).

Query params:

  • offset (number, optional, default 0) — number of items to skip. Must be ≥ 0.
  • count (number, optional, default 10) — page size. Must be 1..100.

Example:

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

curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/withdrawals" \
  --get \
  --data-urlencode "offset=0" \
  --data-urlencode "count=10"

Response 200 (example):

{
  "withdrawals": [
    {
      "id": "2d03607f-5bfc-4354-9721-b7753e805a6a",
      "createdAt": "2026-09-08T07:57:42.482Z",
      "feesUsdVirtual": 4565677889861197300,
      "token": "usdt_bnb",
      "l1Amount": "4565677889861197300",
      "l1Address": "0x018a81aaf8b984d0f0d962580d476505d4130b94",
      "orderCweb": "3009348365199166228726",
      "orderEmbedId": "e47ef0f49c34ff05b13cf7dcb171371db779f61f20b264ff00fb0ef659399233",
      "orderL2Txid": "e47ef0f49c34ff05b13cf7dcb171371db779f61f20b264ff00fb0ef659399233",
      "orderId": "0x088d86b538bd6e8898b23a8ea86bafb1f47dcb752d80ccc39e6bd2ac2bd4ebd0",
      "payoutL1TxId": "b7c907b873a07dacad7d62810d2cc0c7bf8dc89193dcbf958a602976f44a917a",
      "orderStatus": "closed",
      "activityStatus": "COMPLETED",
      "closedAt": "2026-09-08T08:00:01.306Z"
    },
    {
      "id": "56334a36-c835-4744-9ede-1ba2525cde4f",
      "createdAt": "2026-05-12T10:47:12.202Z",
      "feesUsdVirtual": 4568102093523446000,
      "token": null,
      "l1Amount": null,
      "l1Address": null,
      "orderCweb": "3412464381565092346392",
      "orderEmbedId": "97ca5d3805e6efd6b6f1e8cda1cdb6f44da536fb15279a6272b17e36c8a93fe1",
      "orderL2Txid": "97ca5d3805e6efd6b6f1e8cda1cdb6f44da536fb15279a6272b17e36c8a93fe1",
      "orderId": "0x1ee7761947854e02631e3898c8439f5f68bd3ba29fe764d021823be5671bdb96",
      "payoutL1TxId": null,
      "orderStatus": null,
      "activityStatus": null,
      "closedAt": null
    }
  ],
  "offset": 0,
  "count": 10,
  "hasMore": false
}

Notes:

  • id is the withdrawal record identifier (same role as recordId from POST /affiliates/{affiliateId}/withdraw-fees).
  • createdAt / closedAt are ISO-8601 timestamps. closedAt is set when the payout finishes.
  • l1Amount is in the payout token’s smallest units when present.
  • orderEmbedId / orderL2Txid / orderId identify the on-chain payout order.
  • payoutL1TxId is the L1 transaction id of the completed payout.
  • token, l1Amount, l1Address, payoutL1TxId, orderStatus, activityStatus, and closedAt are null until the payout is filled in.
  • offset / count in the response echo the requested pagination.
  • hasMore: true means another page is available; request the next page with offset = offset + count.

Possible errors:

  • 400 INVALID_ARGUMENT (invalid affiliateId format, offset < 0, or count outside 1..100)
  • 404 AFFILIATE_NOT_FOUND

6.6 List affiliate swaps

  • Method/Path: GET /affiliates/{affiliateId}/swaps
  • Purpose: paginated list of swaps attributed to the affiliate. Use each l1TxId with GET /swaps/{l1TxId} (§ 6.7) or GET /getL1TxInfo (§ 6.8) when you need fee amounts or ledger allocations.

Query params:

  • offset (number, optional, default 0) — number of items to skip. Must be ≥ 0.
  • count (number, optional, default 10) — page size. Must be 1..100.

Example:

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

curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/swaps" \
  --get \
  --data-urlencode "offset=0" \
  --data-urlencode "count=10"

Response 200 (example):

{
  "swaps": [
    {
      "l1TxId": "261f445e311b9ad5e0d127b3bd5567a468e54d214040e4933e43eb92a40113a5",
      "fromToken": "BNB",
      "feeBips": 15,
      "timestamp": 1782379394000
    },
    {
      "l1TxId": "2397ff0a4359a3e3bcce9bef5a1a6ff0556dd8a3b31e9b1de9b8306c20fd2934",
      "fromToken": "USDT_BNB",
      "feeBips": 15,
      "timestamp": 1780045460000
    },
    {
      "l1TxId": "4ce85e1e716c95861adc067e02bb4bc324925701aa1799b6ec3f3c4562c20466",
      "fromToken": "BTC",
      "feeBips": 40,
      "timestamp": 1778521106000
    }
  ],
  "offset": 0,
  "count": 10,
  "hasMore": true
}

Notes:

  • fromToken is the source token of the swap (for example BNB, BTC, USDT_BNB, USDT_ETH).
  • feeBips is the affiliate fee for that swap in basis points (1 bp = 0.01%).
  • timestamp is the swap finalization time as a Unix epoch in milliseconds (not the time the swap was initiated).
  • offset / count in the response echo the requested pagination.
  • hasMore: true means another page is available; request the next page with offset = offset + count.

Possible errors:

  • 400 INVALID_ARGUMENT (invalid affiliateId format, offset < 0, or count outside 1..100)
  • 404 AFFILIATE_NOT_FOUND

6.7 Get swap info

  • Method/Path: GET /swaps/{l1TxId}
  • Purpose: returns consolidated swap fee info in a single response — total acquired fees and swap kind — without a per-affiliate breakdown. Use GET /getL1TxInfo (§ 6.8) when you need individual affiliate allocations.

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:

  • 400 INVALID_ARGUMENT (invalid l1TxId format)
  • 404 SWAP_NOT_FOUND

6.8 Get L1 tx fee ledger info

  • Method/Path: GET /getL1TxInfo
  • Purpose: look up affiliate fee allocation for a completed L1 swap from the on-chain fee ledger (preferred for reconciliation).

Query params:

  • l1TxId (string, required) — L1 transaction id (with or without 0x prefix)

Example:

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

curl -sS "$BASE_URL/getL1TxInfo" \
  --get \
  --data-urlencode "l1TxId=$L1TXID"

Response 200 (example):

{
  "l1TxId": "aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000",
  "oracles": {
    "l1TokenToUsd": { "rate": "350000000000", "decimals": 8 },
    "usdtBnbToUsd": { "rate": "100000000", "decimals": 8 }
  },
  "affiliates": [
    {
      "affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901",
      "basisPoints": 25,
      "amountRaw": "612750000000000000",
      "decimals": 18
    }
  ]
}

Notes:

  • oracles / affiliates may be omitted when the ledger claim has no such payload.
  • amountRaw is the affiliate fee share in virtual USDT (BNB) units at decimals precision.

Possible errors:

  • 400 INVALID_ARGUMENT (missing l1TxId)
  • 404 SWAP_NOT_FOUND
  • 500 INTERNAL_ERROR

6.9 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. 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.10 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

Query params (optional):

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

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.9):

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 type WithdrawalToken =
  | 'usdt_bnb'
  | 'usdc_bnb'
  | 'usdt_eth'
  | 'usdc_eth'
  | 'eth'
  | 'btc';

export type WithdrawalMode = 'auto' | 'manual';

export interface RegisterAffiliateRequest {
  evmWallet?: AddressLike;
  btcWallet?: string;
  /** Omit to keep legacy defaults: EVM-only → auto `usdt_bnb`; BTC present → manual. */
  withdrawalMode?: WithdrawalMode;
  /** Required when `withdrawalMode` is `auto`. */
  token?: WithdrawalToken;
}

export interface RegisterAffiliateResponse {
  affiliateId: AddressLike;
  isActivated: boolean;
}

export interface GetAffiliateResponse {
  affiliateId: AddressLike;
  isActivated: boolean;
}

export interface GetFeeBalanceResponse {
  balanceUsd: DecimalString;
}

export interface WithdrawFeesRequest {
  token: WithdrawalToken;
}

export interface WithdrawFeesResponse {
  recordId: string;
  token: WithdrawalToken;
  feesUsdVirtual: number;
  l1Amount: string;
  l1Address: string;
  tokenDecimals: number;
  orderEmbedId: string | null;
  orderL2Txid: string | null;
}

export interface AffiliateWithdrawalItem {
  id: string;
  createdAt: string;
  feesUsdVirtual: number;
  token: WithdrawalToken | null;
  l1Amount: string | null;
  l1Address: string | null;
  orderCweb: string;
  orderEmbedId: string;
  orderL2Txid: string;
  orderId: string;
  payoutL1TxId: string | null;
  orderStatus: string | null;
  activityStatus: string | null;
  closedAt: string | null;
}

export interface ListAffiliateWithdrawalsResponse {
  withdrawals: AffiliateWithdrawalItem[];
  offset: number;
  count: number;
  hasMore: boolean;
}

export interface AffiliateSwapItem {
  l1TxId: string;
  fromToken: string;
  feeBips: number;
  timestamp: number; // swap finalization, Unix epoch ms
}

export interface ListAffiliateSwapsResponse {
  swaps: AffiliateSwapItem[];
  offset: number;
  count: number;
  hasMore: boolean;
}

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

export interface SwapInfoReversedPact {
  kind: 'reversed_pact';
}

export type GetSwapInfoResponse = SwapInfoNormal | SwapInfoReversedPact;

export interface GetL1TxInfoResponse {
  l1TxId: string;
  oracles?: {
    l1TokenToUsd: { rate: string; decimals: number };
    usdtBnbToUsd: { rate: string; decimals: number };
  };
  affiliates?: {
    affiliateId: string;
    basisPoints: number;
    amountRaw: string;
    decimals: number;
  }[];
}

export interface ApiErrorResponse {
  error: {
    code: string;
    message: string;
    details?: Record<string, unknown>;
  };
}
  1. Call POST /affiliates with evmWallet, btcWallet, or both. Omit withdrawalMode for the previous defaults, or set auto / manual explicitly.
BASE_URL="https://api-affiliate.pactswap.io/api/v1"

curl -sS -X POST "$BASE_URL/affiliates" \
  -H "Content-Type: application/json" \
  -d '{
    "evmWallet": "0x1234567890abcdef1234567890abcdef12345678",
    "btcWallet": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
  }'
  1. Store returned affiliateId as primary identifier for all next API calls. Poll GET /affiliates/by-wallet/{wallet} (EVM or Bitcoin address) until isActivated is true before fee balance / withdrawal flows.
# Example of storing returned affiliateId for next requests
AFFILIATE_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
EVM_WALLET="0x1234567890abcdef1234567890abcdef12345678"

curl -sS "$BASE_URL/affiliates/by-wallet/$EVM_WALLET"
  1. For accounting UI, periodically call:
    • GET /affiliates/{affiliateId}/fee-balance
    • GET /affiliates/{affiliateId}/swaps (paginated swap history)
    • GET /affiliates/{affiliateId}/withdrawals (paginated payout history)
curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/fee-balance"

curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/swaps" \
  --get \
  --data-urlencode "offset=0" \
  --data-urlencode "count=10"

curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/withdrawals" \
  --get \
  --data-urlencode "offset=0" \
  --data-urlencode "count=10"
  1. Withdraw fees:
    • Automatic mode (default for EVM-only, or explicit withdrawalMode: "auto"): no action — withdrawal runs in the registered token when the threshold is reached.
    • Manual mode (default when btcWallet is sent, or explicit withdrawalMode: "manual"): call POST /affiliates/{affiliateId}/withdraw-fees with a token that matches a registered wallet.
curl -sS -X POST "$BASE_URL/affiliates/$AFFILIATE_ID/withdraw-fees" \
  -H "Content-Type: application/json" \
  -d '{"token":"usdt_bnb"}'
  1. For swap audit/reconciliation:
    • list attributed swaps via GET /affiliates/{affiliateId}/swaps
    • prefer GET /getL1TxInfo?l1TxId=... for each l1TxId
    • optionally GET /swaps/{l1TxId}
L1TXID="aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"

curl -sS "$BASE_URL/getL1TxInfo" --get --data-urlencode "l1TxId=$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 6, all four contract ids, preset, affiliates, and slippageBips (same values as step 6)
    • 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 wallet on the client side (retry-safe logic). Re-registering an existing EVM or Bitcoin wallet returns 409 AFFILIATE_ALREADY_EXISTS — resolve via GET /affiliates/by-wallet/....
  • Wait for isActivated: true before withdrawal attempts.
  • 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 whichever wallets were sent (evmWallet, btcWallet, or both).
  • 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.
  • After registration, poll activation status every 15-30s until isActivated becomes true (activation involves on-chain funding and may take up to a few minutes).

9.4 Withdrawal checklist

  • Omit withdrawalMode to keep the previous defaults, or set it explicitly. auto needs a token that matches a wallet in the same request (btc ↔ btcWallet, every other token ↔ evmWallet).
  • Automatic mode: monitor fee-balance; payouts happen in the registered token above threshold. Track records via GET /affiliates/{affiliateId}/withdrawals.
  • Manual mode: call withdraw-fees when balance ≥ threshold. btc requires a registered btcWallet; other tokens require evmWallet. A mismatch returns TOKEN_WALLET_MISMATCH. Automatic affiliates return MANUAL_WITHDRAWAL_NOT_ALLOWED.
  • Handle BELOW_THRESHOLD and AFFILIATE_NOT_ACTIVATED explicitly.

9.5 Production readiness checklist (partner side)

  • Store affiliateId as primary partner key.
  • Persist and surface isActivated.
  • Handle both swap variants: normal and reversed_pact.
  • Prefer GET /affiliates/{affiliateId}/swaps for paginated swap history; page with offset/count while hasMore is true.
  • Prefer GET /affiliates/{affiliateId}/withdrawals for paginated payout history (fields may be null until the payout completes).
  • Prefer getL1TxInfo for fee reconciliation on completed swaps.
  • 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?