@switch-win/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,950 @@
1
+ # Switch SDK
2
+
3
+ > **Official integration kit for the Switch DEX Aggregator on PulseChain**
4
+
5
+ Everything partners need to integrate Switch swaps and limit orders — API docs, TypeScript types, ABIs, constants, and ready-to-use examples.
6
+
7
+ **Swap API:** `https://quote.switch.win`  |  **Limit Order API:** `https://api.switch.win`  |  **Chain:** PulseChain (369)
8
+
9
+ ---
10
+
11
+ ## Contents
12
+
13
+ ```
14
+ Switch-SDK/
15
+ ├── README.md # This file — swap API & general integration guide
16
+ ├── LIMIT-ORDERS.md # Limit order integration guide (includes PLSFlow)
17
+ ├── src/
18
+ │ ├── index.ts # Main entry — re-exports everything
19
+ │ ├── types.ts # TypeScript types (swap + limit orders)
20
+ │ ├── constants.ts # Addresses, ABIs, EIP-712, PLSFlow config
21
+ │ └── limit-orders.ts # Limit order helpers (build, sign, submit, query, PLSFlow)
22
+ ├── abi/
23
+ │ ├── SwitchRouterABI.json # Full SwitchRouter contract ABI
24
+ │ └── SwitchLimitOrderABI.json # Full SwitchLimitOrder contract ABI
25
+ ├── examples/
26
+ │ ├── swap-ethers.ts # Complete swap example (ethers.js v6)
27
+ │ ├── swap-web3py.py # Complete swap example (web3.py)
28
+ │ ├── limit-order-ethers.ts # Complete limit order example (ethers.js v6)
29
+ │ ├── nextjs-proxy.ts # Next.js API route proxy for key security
30
+ │ └── react-hooks.tsx # React hooks for quote, adapters & tax
31
+ └── package.json
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Table of Contents
37
+
38
+ ### Swaps
39
+
40
+ 1. [Quickstart](#quickstart)
41
+ 2. [Authentication](#authentication)
42
+ 3. [Swap Integration Flow](#swap-integration-flow)
43
+ 4. [Swap API Reference](#swap-api-reference)
44
+ 5. [Error Handling](#error-handling)
45
+ 6. [Partner Fee Sharing](#partner-fee-sharing)
46
+
47
+ ### Limit Orders
48
+
49
+ 7. [Limit Orders](#limit-orders) — full guide in [`LIMIT-ORDERS.md`](LIMIT-ORDERS.md)
50
+
51
+ ### General
52
+
53
+ 8. [Constants & Addresses](#constants--addresses)
54
+ 9. [Full Integration Examples](#full-integration-examples)
55
+ 10. [Rate Limits](#rate-limits)
56
+ 11. [Support](#support)
57
+
58
+ ---
59
+
60
+ ## Quickstart
61
+
62
+ Get a swap quote and execute it in **three steps**:
63
+
64
+ ```bash
65
+ # 1. Get quote with tx calldata
66
+ curl -H "x-api-key: YOUR_KEY" \
67
+ "https://quote.switch.win/swap/quote?network=pulsechain&from=0xA1077a294dDE1B09bB078844df40758a5D0f9a27&to=0x95B303987A60C71504D99Aa1b13B4DA07b0790ab&amount=1000000000000000000&sender=0xYOUR_WALLET&slippage=100"
68
+
69
+ # 2. Approve the SwitchRouter to spend your input token (ERC-20 only, skip for native PLS)
70
+
71
+ # 3. Send the transaction using the `tx` object from the response:
72
+ # { to: "0x31077...", data: "0x...", value: "0" }
73
+ ```
74
+
75
+ ### Using the SDK types (TypeScript)
76
+
77
+ ```ts
78
+ import { SWITCH_ROUTER, QUOTE_ENDPOINT } from "@switch-win/sdk/constants";
79
+ import type { BestPathResponse } from "@switch-win/sdk/types";
80
+
81
+ // 1. Get quote
82
+ const res = await fetch(
83
+ `${QUOTE_ENDPOINT}?network=pulsechain&from=${fromToken}&to=${toToken}&amount=${amount}&sender=${wallet}&slippage=100`,
84
+ { headers: { "x-api-key": KEY } },
85
+ );
86
+ const quote: BestPathResponse = await res.json();
87
+
88
+ // 2. Approve SwitchRouter (ERC-20 only — skip for native PLS)
89
+ const token = new ethers.Contract(fromToken, ["function approve(address,uint256)"], signer);
90
+ await (await token.approve(SWITCH_ROUTER, amount)).wait();
91
+
92
+ // 3. Send the swap
93
+ await signer.sendTransaction(quote.tx);
94
+ ```
95
+
96
+ For a production integration with tax token handling, adapter filtering, and fee mode selection, see [Swap Integration Flow](#swap-integration-flow) and the [full examples](examples/).
97
+
98
+ ---
99
+
100
+ ## Authentication
101
+
102
+ Every request **must** include an API key via one of:
103
+
104
+ | Method | Header | Value |
105
+ |---|---|---|
106
+ | Custom header | `x-api-key` | `<your-api-key>` |
107
+ | Bearer token | `Authorization` | `Bearer <your-api-key>` |
108
+
109
+ > Contact the Switch team to obtain an API key.
110
+
111
+ **Example:**
112
+
113
+ ```bash
114
+ curl -H "x-api-key: YOUR_KEY" "https://quote.switch.win/swap/quote?network=pulsechain&from=0xA1077a294dDE1B09bB078844df40758a5D0f9a27&to=0x95B303987A60C71504D99Aa1b13B4DA07b0790ab&amount=1000000000000000000&sender=0xYourWallet"
115
+ ```
116
+
117
+ ### Keeping Your API Key Secure
118
+
119
+ Your API key is a secret credential — **never expose it in client-side code** (browser JavaScript, mobile app bundles, public repositories, etc.). Anyone who obtains your key can make requests on your behalf and consume your rate limit.
120
+
121
+ **Recommended architecture:** Route all Switch API calls through your own backend server, which holds the key privately and proxies requests to Switch.
122
+
123
+ ```
124
+ ┌──────────┐ ┌─────────────────┐ ┌─────────────────┐
125
+ │ Browser │──────▶│ Your Backend │──────▶│ Switch API │
126
+ │ / App │ (no │ (holds key) │ x-api- │ quote.switch.win│
127
+ │ │ key) │ │ key │ │
128
+ └──────────┘ └─────────────────┘ └─────────────────┘
129
+ ```
130
+
131
+ **Best practices:**
132
+
133
+ | Do | Don't |
134
+ |---|---|
135
+ | Store the key in server-side environment variables (`process.env`, `.env` files excluded from git) | Embed the key in frontend JavaScript, HTML, or mobile app code |
136
+ | Use a backend proxy endpoint (e.g. `/api/quote`) that attaches the key before forwarding to Switch | Commit `.env` files or key values to version control |
137
+ | Restrict your backend proxy with CORS to only your own domain(s) | Log or print API keys in production |
138
+ | Rotate your key immediately if you suspect it has been leaked | Share a single key across unrelated projects |
139
+
140
+ > See [`examples/nextjs-proxy.ts`](examples/nextjs-proxy.ts) for a ready-to-use Next.js API route that keeps your key server-side.
141
+
142
+ ---
143
+
144
+ ## Swap Integration Flow
145
+
146
+ For the most accurate swap quotes — especially with tax tokens — follow this **three-step flow**:
147
+
148
+ ```
149
+ ┌────────────────────────────────────────────────────────────────────────────┐
150
+ │ Step 1: GET /swap/adapters │
151
+ │ → Get available DEXes (cache for hours — rarely changes) │
152
+ │ → Optionally let users disable specific DEXes │
153
+ ├────────────────────────────────────────────────────────────────────────────┤
154
+ │ Step 2: GET /swap/checkTax?token=<fromToken>&network=pulsechain │
155
+ │ GET /swap/checkTax?token=<toToken>&network=pulsechain │
156
+ │ → Detect transfer taxes on both tokens │
157
+ │ → Determine feeOnOutput based on tax results + your fee strategy │
158
+ ├────────────────────────────────────────────────────────────────────────────┤
159
+ │ Step 3: GET /swap/quote?...&feeOnOutput=true/false&adapters=0,3,6 │
160
+ │ → Pass feeOnOutput + adapter filter for an exact expectedOutputAmount │
161
+ │ → Display quote and execute swap │
162
+ └────────────────────────────────────────────────────────────────────────────┘
163
+ ```
164
+
165
+ ### Why `feeOnOutput` matters for accuracy
166
+
167
+ The SwitchRouter contract supports two fee modes:
168
+
169
+ - **`feeOnOutput=true`** — The full input is routed through DEX pools, then the protocol fee is deducted from the output.
170
+ - **`feeOnOutput=false`** — The protocol fee is deducted from the input *before* routing, and the reduced amount enters the DEX pools.
171
+
172
+ Due to AMM curve concavity (Jensen's inequality), `AMM(amount × 0.9975) ≠ AMM(amount) × 0.9975`. This means the backend **must know which mode you'll use** to compute an exact `expectedOutputAmount`. If the backend routes as `feeOnOutput=true` but you send the `tx` (fee on input), the expected output will be slightly over-estimated (always in the user's favor — they receive more than quoted).
173
+
174
+ By passing `feeOnOutput` explicitly, the backend adjusts routing to match the actual on-chain execution, giving you a spot-on estimate.
175
+
176
+ ### Determining `feeOnOutput` with tax tokens
177
+
178
+ When tax tokens are involved, use the `checkTax` results to decide:
179
+
180
+ ```ts
181
+ import type { CheckTaxResponse } from "@switch-win/sdk/types";
182
+ import { NATIVE_PLS, WPLS } from "@switch-win/sdk/constants";
183
+
184
+ function determineFeeOnOutput(
185
+ fromToken: string,
186
+ toToken: string,
187
+ fromTax: CheckTaxResponse,
188
+ toTax: CheckTaxResponse,
189
+ ): boolean {
190
+ const plsAddresses = [NATIVE_PLS.toLowerCase(), WPLS.toLowerCase()];
191
+ const fromAddr = fromToken.toLowerCase();
192
+ const toAddr = toToken.toLowerCase();
193
+
194
+ const isSellTax = fromTax.isTaxToken && fromTax.sellTaxBps > 0;
195
+ const isBuyTax = toTax.isTaxToken && toTax.buyTaxBps > 0;
196
+
197
+ // Both tokens are tax tokens — ALWAYS fee on input.
198
+ // See "Why fee-on-input for tax tokens?" below for a detailed explanation.
199
+ if (isSellTax && isBuyTax) return false;
200
+
201
+ // Only output token is tax — fee on input (avoids router holding output tokens)
202
+ if (isBuyTax) return false;
203
+
204
+ // Only input token is tax — fee on output (fee collected in the non-tax output token)
205
+ if (isSellTax) return true;
206
+
207
+ // No tax tokens — decide based on PLS preference:
208
+ // Buying PLS → fee on output (collect PLS)
209
+ if (plsAddresses.includes(toAddr)) return true;
210
+ // Selling PLS → fee on input (collect PLS)
211
+ if (plsAddresses.includes(fromAddr)) return false;
212
+
213
+ // Default: fee on output
214
+ return true;
215
+ }
216
+ ```
217
+
218
+ ### Why fee-on-input for tax tokens?
219
+
220
+ Tax tokens (fee-on-transfer tokens) charge a percentage on every `transfer()` / `transferFrom()` call. The SwitchRouter's settlement path differs based on the fee mode:
221
+
222
+ **`feeOnOutput=false` (fee on input) — recommended for tax tokens:**
223
+ ```
224
+ User ──transferFrom──▶ Router ──swap──▶ Pool ──transfer──▶ User
225
+ (1 sell tax) (1 buy tax)
226
+ Fee portion: User ──transferFrom──▶ FeeClaimer (1 sell tax on fee amount)
227
+ ```
228
+ The output token is transferred **once** (pool → user directly). Only 1 buy tax hit.
229
+
230
+ **`feeOnOutput=true` (fee on output) — avoid when output is a tax token:**
231
+ ```
232
+ User ──transferFrom──▶ Router ──swap──▶ Pool ──transfer──▶ Router ──transfer──▶ User
233
+ (1 sell tax) (1 buy tax) (2nd buy tax!)
234
+ Fee portion: Router ──transfer──▶ FeeClaimer (3rd buy tax on fee amount!)
235
+ ```
236
+ The output token is transferred **three times** through different addresses, each incurring a buy tax. The user receives significantly less than expected.
237
+
238
+ **Bottom line:** When the output token has a buy tax, always use `feeOnOutput=false` so the router routes output directly to the user in a single transfer. This applies to both regular swaps and limit orders.
239
+
240
+ ### Executing the Swap
241
+
242
+ Once you have a quote, execute it in two steps:
243
+
244
+ #### Step 1 — Approve Token Spend (ERC-20 inputs only)
245
+
246
+ If the input token is an **ERC-20** (not native PLS), the user must approve the SwitchRouter contract to spend `amount` tokens before submitting the swap.
247
+
248
+ Before sending an approval transaction, check whether the user already has sufficient allowance to avoid wasting gas on a redundant approve:
249
+
250
+ ```ts
251
+ import { SWITCH_ROUTER, ERC20_ABI } from "@switch-win/sdk/constants";
252
+
253
+ const token = new ethers.Contract(fromToken, ERC20_ABI, signer);
254
+ const currentAllowance = await token.allowance(sender, SWITCH_ROUTER);
255
+
256
+ if (currentAllowance < BigInt(amount)) {
257
+ const approveTx = await token.approve(SWITCH_ROUTER, amount);
258
+ await approveTx.wait();
259
+ }
260
+ ```
261
+
262
+ > **Tip:** If the input is native **PLS**, no approval is needed — the value is sent with the transaction.
263
+
264
+ #### Step 2 — Send the Transaction
265
+
266
+ Use the `tx` object from the quote response directly:
267
+
268
+ ```js
269
+ const { tx } = quoteResponse;
270
+
271
+ const txResponse = await signer.sendTransaction({
272
+ to: tx.to,
273
+ data: tx.data,
274
+ value: tx.value, // "0" for ERC-20 inputs, amountIn for native PLS
275
+ });
276
+
277
+ const receipt = await txResponse.wait();
278
+ console.log("Swap confirmed:", receipt.hash);
279
+ ```
280
+
281
+ That's it. The `tx.data` already encodes the correct `goSwitch()` call with your routes, slippage protection, fee, and partner address.
282
+
283
+ The `tx` object does not include a `gas` field. Most wallet libraries (ethers.js, web3.py, MetaMask) auto-estimate gas when `gas` is omitted. For multi-hop split swaps, `500,000`–`1,000,000` gas is typically sufficient:
284
+
285
+ ```js
286
+ const txResponse = await signer.sendTransaction({
287
+ ...quote.tx,
288
+ gasLimit: 800_000, // optional: override if auto-estimate is unreliable
289
+ });
290
+ ```
291
+
292
+ > See [`examples/swap-ethers.ts`](examples/swap-ethers.ts) for a complete working example.
293
+
294
+ ### Important Notes
295
+
296
+ **Quote freshness:** Quotes reflect current on-chain liquidity and are **cached for ~10 seconds**. DEX prices can shift between when you fetch a quote and when the transaction lands on-chain — this is what `minAmountOut` (slippage protection) guards against. Fetch a fresh quote immediately before sending the transaction.
297
+
298
+ **Token amounts are in wei:** All amounts (`amount`, `totalAmountIn`, `totalAmountOut`, `minAmountOut`, etc.) are **raw integer strings in the token's smallest unit** (wei). To convert a human-readable amount to wei, multiply by `10^decimals`:
299
+
300
+ | Token | Decimals | 1.0 token in wei |
301
+ |---|---|---|
302
+ | PLS / WPLS | 18 | `"1000000000000000000"` |
303
+ | USDC (bridged) | 6 | `"1000000"` |
304
+ | USDT (bridged) | 6 | `"1000000"` |
305
+
306
+ ```js
307
+ // ethers.js v6
308
+ const amountWei = ethers.parseUnits("1.5", 18).toString(); // "1500000000000000000"
309
+ ```
310
+
311
+ **Native PLS vs WPLS:**
312
+ - Use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` (the native sentinel address) to swap **native PLS**.
313
+ - The router handles wrapping/unwrapping automatically:
314
+ - **Selling native PLS:** Send PLS as `msg.value` — the router wraps it to WPLS internally.
315
+ - **Buying native PLS:** The router unwraps WPLS and sends native PLS to the recipient.
316
+ - If you want to swap **WPLS the ERC-20 token** directly (without wrapping/unwrapping), use the WPLS address `0xA1077a294dDE1B09bB078844df40758a5D0f9a27`.
317
+
318
+ ---
319
+
320
+ ## Swap API Reference
321
+
322
+ ### List Adapters
323
+
324
+ ```
325
+ GET /swap/adapters?network=pulsechain
326
+ ```
327
+
328
+ Returns all available DEX adapters with their on-chain indices and contract addresses. Use the indices to filter which DEXes the router considers via the `adapters` query param on `/swap/quote`.
329
+
330
+ | Param | Required | Type | Description |
331
+ |---|---|---|---|
332
+ | `network` | **Yes** | string | Target blockchain network. Currently only `"pulsechain"` is supported. |
333
+
334
+ #### Authentication
335
+
336
+ Requires API key (same as all endpoints).
337
+
338
+ #### Example Request
339
+
340
+ ```bash
341
+ curl -H "x-api-key: YOUR_KEY" "https://quote.switch.win/swap/adapters?network=pulsechain"
342
+ ```
343
+
344
+ #### Example Response
345
+
346
+ ```json
347
+ {
348
+ "adapters": [
349
+ { "index": 0, "name": "UniswapV2", "address": "0x8730c3e2cf2c8cda8e6166837a1ed26f46aa9e59" },
350
+ { "index": 1, "name": "SushiV2", "address": "0xff6b56d3f444eb5b7fa1db047f57140c84810376" },
351
+ { "index": 2, "name": "PulseXV1", "address": "0xc8c50c6fac75b0d082d5b99f52581ca25ccb719f" },
352
+ { "index": 3, "name": "PulseXV2", "address": "0x5d3ef85adcf1532e9111e4a6a331f6e5ddfb2d25" },
353
+ { "index": 4, "name": "9inchV2", "address": "0xcaa612cde3d3fbe97be97eb5f79bc91597432d55" },
354
+ { "index": 5, "name": "DextopV2", "address": "0x72d8f2b9cfb9cfd73403288a126578b7e31ee6ac" },
355
+ { "index": 6, "name": "UniswapV3", "address": "0x7bbb21bdc6c94b90367a0b1835e1f233ab39d6a7" },
356
+ { "index": 7, "name": "9mmV3", "address": "0x42cf246e6271feb2c3e6d14fc405ed0bf5152be2" },
357
+ { "index": 8, "name": "9inchV3", "address": "0xbb017a0da988b72f202cd19655361cdb57383802" },
358
+ { "index": 9, "name": "pDexV3", "address": "0xb2141992cb4500e7099bc6d7d3da405e63259dcf" },
359
+ { "index": 10, "name": "DextopV3", "address": "0xe3073faaed1490eab7b3e65f2a1659c9853d5379" },
360
+ { "index": 11, "name": "Phux", "address": "0xe9a3aefd86b9230abc980c012b28e39f8561682c" },
361
+ { "index": 12, "name": "Tide", "address": "0x235ba14f6df83c17353e410e5a2dcc052a5a0f64" },
362
+ { "index": 13, "name": "PulseXStable", "address": "0xac4da986100724983042ec28c28db243e2f828cb" }
363
+ ]
364
+ }
365
+ ```
366
+
367
+ #### Using adapter indices to filter routing
368
+
369
+ Pass adapter indices as a comma-separated `adapters` param to `/swap/quote`:
370
+
371
+ ```bash
372
+ # Route only through PulseXV2 (index 3):
373
+ GET /swap/quote?...&adapters=3
374
+
375
+ # Route through PulseXV2 + UniswapV3:
376
+ GET /swap/quote?...&adapters=3,6
377
+
378
+ # Exclude nothing (default — omit the param):
379
+ GET /swap/quote?...
380
+ ```
381
+
382
+ This is useful for building a DEX toggle UI where users can enable/disable specific liquidity sources.
383
+
384
+ ### Check Token Tax
385
+
386
+ ```
387
+ GET /swap/checkTax
388
+ ```
389
+
390
+ Detects whether a token has a fee-on-transfer mechanism (tax token) and returns the buy/sell tax rates in basis points. Use this **before** fetching a quote to:
391
+
392
+ 1. Display a tax warning to the user
393
+ 2. Determine the optimal `feeOnOutput` mode
394
+ 3. Pass that mode to `/swap/quote` for an exact estimate
395
+
396
+ #### Query Parameters
397
+
398
+ | Parameter | Required | Type | Description |
399
+ |---|---|---|---|
400
+ | `token` | **Yes** | address | Token address to check (0x + 40 hex chars) |
401
+ | `network` | No | string | Target blockchain. Currently only `"pulsechain"`. |
402
+
403
+ #### Latency
404
+
405
+ | Scenario | Typical Latency |
406
+ |---|---|
407
+ | Cached result (previously checked token) | **< 1 ms** |
408
+ | Uncached (first check for this token) | **50–200 ms** |
409
+
410
+ Tax results are cached server-side, so repeated checks for the same token are essentially free. You can safely call this for both `from` and `to` tokens in parallel before every quote.
411
+
412
+ #### Example Request
413
+
414
+ ```bash
415
+ curl -H "x-api-key: YOUR_KEY" \
416
+ "https://quote.switch.win/swap/checkTax?token=0x95B303987A60C71504D99Aa1b13B4DA07b0790ab&network=pulsechain"
417
+ ```
418
+
419
+ #### Example Response (non-tax token)
420
+
421
+ ```json
422
+ {
423
+ "token": "0x95b303987a60c71504d99aa1b13b4da07b0790ab",
424
+ "isTaxToken": false,
425
+ "buyTaxBps": 0,
426
+ "sellTaxBps": 0
427
+ }
428
+ ```
429
+
430
+ #### Example Response (tax token)
431
+
432
+ ```json
433
+ {
434
+ "token": "0x1234567890abcdef1234567890abcdef12345678",
435
+ "isTaxToken": true,
436
+ "buyTaxBps": 500,
437
+ "sellTaxBps": 300
438
+ }
439
+ ```
440
+
441
+ In this example, the token has a 5% buy tax and a 3% sell tax.
442
+
443
+ #### Tax Tokens Deep Dive
444
+
445
+ Some tokens on PulseChain charge a percentage fee on every `transfer()` call — commonly called **tax tokens** or **fee-on-transfer tokens**. When you swap into or out of one of these tokens, the actual amount received differs from the quoted DEX output because the token's own contract skims a fee during the transfer.
446
+
447
+ The API automatically detects tax tokens via empirical simulation and returns the tax rates in the quote response:
448
+
449
+ ```jsonc
450
+ {
451
+ // Selling a token with 1.2% sell tax
452
+ "fromTokenTax": { "isTaxToken": true, "buyTaxBps": 100, "sellTaxBps": 120 },
453
+ // Buying a non-tax token
454
+ "toTokenTax": { "isTaxToken": false, "buyTaxBps": 0, "sellTaxBps": 0 },
455
+ // minAmountOut already accounts for the 1.2% sell tax + user slippage
456
+ "minAmountOut": "...",
457
+ // Effective slippage = user slippage (0.5%) + sell tax (1.2%) = 1.7%
458
+ "effectiveSlippageBps": 170,
459
+ "effectiveSlippagePercent": "1.7"
460
+ }
461
+ ```
462
+
463
+ **How it works:**
464
+
465
+ - If `fromToken` is a tax token, its **`sellTaxBps`** reduces the effective input reaching the DEX pools. This is the relevant tax when the token is being **sold** (sent out of the user's wallet).
466
+ - If `toToken` is a tax token, its **`buyTaxBps`** reduces what the user ultimately receives. This is the relevant tax when the token is being **bought** (received into the user's wallet).
467
+ - Both taxes (if applicable) **plus** the user's slippage tolerance are factored into `minAmountOut`, so the transaction won't revert unexpectedly.
468
+
469
+ **Key fields for integrators:**
470
+
471
+ | Field | Description |
472
+ |---|---|
473
+ | `fromTokenTax.sellTaxBps` | Sell tax applied when `fromToken` is the input (e.g. 120 = 1.2%) |
474
+ | `toTokenTax.buyTaxBps` | Buy tax applied when `toToken` is the output (e.g. 500 = 5%) |
475
+ | `effectiveSlippageBps` | User slippage + sell tax + buy tax in basis points |
476
+ | `effectiveSlippagePercent` | Same as above, as a human-readable `%` string (e.g. `"1.7"`) |
477
+ | `expectedOutputAmount` | Best estimate of what the user actually receives (after taxes + fee, before slippage). Display this in your UI. |
478
+ | `minAmountOut` | Minimum output with taxes + fee + slippage already applied — use this directly |
479
+
480
+ **UI recommendations:**
481
+
482
+ - Display a warning when `fromTokenTax.isTaxToken` or `toTokenTax.isTaxToken` is `true`.
483
+ - Show the tax percentage to the user (e.g. "1.2% sell tax") so they understand the impact.
484
+ - Display `effectiveSlippagePercent` as the total slippage indicator — e.g. "Slippage: 1.7% (includes 1.2% sell tax)".
485
+ - Use `minAmountOut` directly from the API response — it already includes taxes, fees, and slippage. **Do not re-compute it client-side.**
486
+
487
+ ```ts
488
+ // Example: display effective slippage in your UI
489
+ const label = `Slippage: ${quote.effectiveSlippagePercent}%`;
490
+ const fromIsTax = quote.fromTokenTax?.isTaxToken ?? false;
491
+ const toIsTax = quote.toTokenTax?.isTaxToken ?? false;
492
+
493
+ if (fromIsTax) {
494
+ // Show: "Slippage: 1.7% (includes 1.2% sell tax)"
495
+ label += ` (includes ${quote.fromTokenTax!.sellTaxBps / 100}% sell tax)`;
496
+ }
497
+ if (toIsTax) {
498
+ // Show: "Slippage: 5.5% (includes 5% buy tax)"
499
+ label += ` (includes ${quote.toTokenTax!.buyTaxBps / 100}% buy tax)`;
500
+ }
501
+ ```
502
+
503
+ ### Get Swap Quote
504
+
505
+ ```
506
+ GET /swap/quote
507
+ ```
508
+
509
+ Returns the optimal split-route for a swap and (optionally) a ready-to-send transaction object.
510
+
511
+ #### Query Parameters
512
+
513
+ | Parameter | Required | Type | Default | Description |
514
+ |---|---|---|---|---|
515
+ | `network` | **Yes** | string | — | Target blockchain network. Currently only `"pulsechain"` is supported. |
516
+ | `from` | **Yes** | address | — | Input token address. Use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` for native PLS. |
517
+ | `to` | **Yes** | address | — | Output token address. Use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` for native PLS. |
518
+ | `amount` | **Yes** | string | — | Input amount in **wei** (raw integer string, no decimals). Max: 10²⁷. |
519
+ | `sender` | No* | address | — | Sender wallet address. **Required to receive `tx` calldata in the response.** |
520
+ | `receiver` | No | address | `sender` | Custom recipient address. If omitted, output tokens are sent to `sender`. |
521
+ | `slippage` | No | integer | `50` | Slippage tolerance in **basis points** (bps). `50` = 0.50 %. Range: `0`–`5000`. |
522
+ | `fee` | No | integer | `30` | Protocol fee in basis points (0.30 %). Range: `30`–`100`. Defaults to `30` if omitted. |
523
+ | `partnerAddress` | No | address | `0x0…0` | Your partner wallet to receive 50 % of collected fees. Omit or pass `0x0` for no partner. |
524
+ | `feeOnOutput` | No | string | `"false"` | Controls fee mode. `"true"` = fee deducted from output. `"false"` = fee deducted from input (default). Affects `expectedOutputAmount` accuracy — see [Swap Integration Flow](#swap-integration-flow). |
525
+ | `adapters` | No | string | — | Comma-separated adapter indices to restrict routing (e.g. `"3"` for PulseXV2 only, `"3,6"` for PulseXV2 + UniswapV3). Get available indices from [List Adapters](#list-adapters). |
526
+
527
+ > \* If `sender` is omitted, the response will still contain routing data, `minAmountOut`, and tax info, but the `tx` object will be absent. This is useful for **showing estimated swap output before the user connects their wallet** — you can display prices, routes, and tax warnings without requiring a wallet connection. Once the user connects, re-fetch the quote with `sender` to get the ready-to-send `tx` object.
528
+
529
+ #### Example Request
530
+
531
+ ```
532
+ GET /swap/quote?network=pulsechain&from=0xA1077a294dDE1B09bB078844df40758a5D0f9a27&to=0x95B303987A60C71504D99Aa1b13B4DA07b0790ab&amount=1000000000000000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&slippage=100&fee=30&partnerAddress=0xYourPartnerWallet&feeOnOutput=true
533
+ ```
534
+
535
+ #### Example Response
536
+
537
+ ```jsonc
538
+ {
539
+ "fromToken": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
540
+ "toToken": "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab",
541
+ "receiver": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
542
+ "totalAmountIn": "1000000000000000000",
543
+ "totalAmountOut": "52934810000000000000",
544
+ "expectedOutputAmount": "52801533000000000000", // after sell tax + protocol fee + buy tax (no slippage)
545
+ "minAmountOut": "52405461900000000000", // totalAmountOut adjusted for tax + fee + slippage
546
+ "fromTokenTax": { "isTaxToken": false, "buyTaxBps": 0, "sellTaxBps": 0 },
547
+ "toTokenTax": { "isTaxToken": false, "buyTaxBps": 0, "sellTaxBps": 0 },
548
+ "paths": [ /* ... detailed path info ... */ ],
549
+ "routeAllocation": {
550
+ "amountIn": "1000000000000000000",
551
+ "totalAmountOut": "52934810000000000000",
552
+ "routes": [
553
+ {
554
+ "amountIn": "600000000000000000",
555
+ "hops": [
556
+ {
557
+ "tokenIn": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
558
+ "tokenOut": "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab",
559
+ "totalAmountOut": "31893000000000000000",
560
+ "legs": [
561
+ {
562
+ "adapter": "PulseXV2",
563
+ "amountIn": "600000000000000000",
564
+ "amountOut": "31893000000000000000"
565
+ }
566
+ ]
567
+ }
568
+ ]
569
+ },
570
+ {
571
+ "amountIn": "400000000000000000",
572
+ "hops": [
573
+ {
574
+ "tokenIn": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
575
+ "tokenOut": "0x57fde0a71132198bbec939b98976993d8d89d225",
576
+ "totalAmountOut": "2100000000",
577
+ "legs": [
578
+ {
579
+ "adapter": "UniswapV3",
580
+ "amountIn": "400000000000000000",
581
+ "amountOut": "2100000000"
582
+ }
583
+ ]
584
+ },
585
+ {
586
+ "tokenIn": "0x57fde0a71132198bbec939b98976993d8d89d225",
587
+ "tokenOut": "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab",
588
+ "totalAmountOut": "21041810000000000000",
589
+ "legs": [
590
+ {
591
+ "adapter": "9inchV2",
592
+ "amountIn": "2100000000",
593
+ "amountOut": "21041810000000000000"
594
+ }
595
+ ]
596
+ }
597
+ ]
598
+ }
599
+ ]
600
+ },
601
+ // Transaction object — only present when `sender` is provided
602
+ "tx": {
603
+ "to": "0x31077B259e7fEEB7bE39bF298274BaE94Ee57B7a",
604
+ "data": "0x...", // ABI-encoded goSwitch() calldata with feeOnOutput = false
605
+ "value": "0" // "0" for ERC-20 input; amountIn for native PLS input
606
+ },
607
+ // Same swap but with fee taken from the output token instead
608
+ "txFeeOnOutput": {
609
+ "to": "0x31077B259e7fEEB7bE39bF298274BaE94Ee57B7a",
610
+ "data": "0x...", // ABI-encoded goSwitch() calldata with feeOnOutput = true
611
+ "value": "0"
612
+ }
613
+ }
614
+ ```
615
+
616
+ ### Response Schema
617
+
618
+ > TypeScript interfaces for all types below are available in [`src/types.ts`](src/types.ts).
619
+
620
+ #### `BestPathResponse`
621
+
622
+ | Field | Type | Description |
623
+ |---|---|---|
624
+ | `fromToken` | `string` | Input token address |
625
+ | `toToken` | `string` | Output token address |
626
+ | `receiver` | `string?` | Recipient address for output tokens. Same as `sender` unless a custom `receiver` was specified. |
627
+ | `totalAmountIn` | `string` | Total input amount in wei |
628
+ | `totalAmountOut` | `string` | Raw DEX output in wei — reflects price impact only (before taxes, fees, and slippage) |
629
+ | `expectedOutputAmount` | `string` | Expected output the user will actually receive, in wei. Accounts for sell tax, protocol fee, and buy tax — but NOT slippage. This is the most accurate value to display as the estimated received amount. When `feeOnOutput` is passed correctly, this is **exact** (matches on-chain execution). |
630
+ | `minAmountOut` | `string` | Minimum acceptable output after token taxes, fee, and slippage. When tax tokens are involved: `totalAmountOut × (10000 − sellTaxBps) / 10000 × (10000 − feeBps) / 10000 × (10000 − buyTaxBps) / 10000 × (10000 − slippageBps) / 10000`. This is the value encoded in the `tx` calldata as `_minTotalAmountOut`. |
631
+ | `paths` | `SwapPath[]` | Human-readable path descriptions |
632
+ | `routeAllocation` | `RouteAllocationPlan` | Structured route breakdown (matches on-chain structs) |
633
+ | `tx` | `SwapTransaction?` | Ready-to-send transaction (fee on input). Only present when `sender` is provided. |
634
+ | `txFeeOnOutput` | `SwapTransaction?` | Ready-to-send transaction (fee on output). Only present when `sender` is provided. Choose between `tx` and `txFeeOnOutput` at send time. |
635
+ | `fromTokenTax` | `TokenTaxResponse?` | Transfer tax info for the input token. See [Tax Tokens Deep Dive](#tax-tokens-deep-dive). |
636
+ | `toTokenTax` | `TokenTaxResponse?` | Transfer tax info for the output token. See [Tax Tokens Deep Dive](#tax-tokens-deep-dive). |
637
+ | `effectiveSlippageBps` | `number` | User slippage + `fromTokenTax.sellTaxBps` + `toTokenTax.buyTaxBps` combined in basis points. |
638
+ | `effectiveSlippagePercent` | `string` | Same as `effectiveSlippageBps` as a human-readable percentage (e.g. `"1.7"`). |
639
+
640
+ #### `SwapPath`
641
+
642
+ A human-readable summary of each route split. Useful for display purposes (e.g. showing the user "60% via PulseXV2, 40% via UniswapV3").
643
+
644
+ | Field | Type | Description |
645
+ |---|---|---|
646
+ | `adapter` | `string` | Human-readable DEX adapter name (e.g. `"PulseXV2"`, `"UniswapV3"`) |
647
+ | `amountIn` | `string` | Input amount for this path (wei) |
648
+ | `amountOut` | `string` | Expected output for this path (wei) |
649
+ | `path` | `string[]` | Ordered list of token addresses traversed (e.g. `[tokenIn, intermediate, tokenOut]`) |
650
+ | `adapters` | `string[]` | Human-readable adapter names used at each hop |
651
+ | `percentage` | `number?` | Percentage of total input routed through this path |
652
+ | `legs` | `SwapPathLeg[]?` | Detailed breakdown of each hop-leg in this path |
653
+
654
+ #### `SwapPathLeg`
655
+
656
+ | Field | Type | Description |
657
+ |---|---|---|
658
+ | `tokenIn` | `string` | Leg input token |
659
+ | `tokenOut` | `string` | Leg output token |
660
+ | `adapter` | `string?` | Human-readable DEX adapter name |
661
+ | `amountIn` | `string` | Input amount (wei) |
662
+ | `amountOut` | `string` | Output amount (wei) |
663
+ | `percentage` | `number?` | Percentage of the hop routed through this adapter |
664
+
665
+ #### `SwapTransaction`
666
+
667
+ | Field | Type | Description |
668
+ |---|---|---|
669
+ | `to` | `string` | SwitchRouter contract address |
670
+ | `data` | `string` | ABI-encoded `goSwitch()` calldata |
671
+ | `value` | `string` | Native PLS to send (wei). `"0"` for ERC-20 input tokens. |
672
+
673
+ #### `TokenTaxResponse`
674
+
675
+ Present on every quote response as `fromTokenTax` and `toTokenTax`. Reports whether each token charges a fee on transfer ("tax token").
676
+
677
+ | Field | Type | Description |
678
+ |---|---|---|
679
+ | `isTaxToken` | `boolean` | `true` if the token has a non-zero transfer tax |
680
+ | `buyTaxBps` | `number` | Buy tax in basis points — applied when the token is *acquired* (i.e. it is the output token). `500` = 5 %. |
681
+ | `sellTaxBps` | `number` | Sell tax in basis points — applied when the token is *sold* (i.e. it is the input token). `500` = 5 %. |
682
+
683
+ > When a tax token is involved, `minAmountOut` already accounts for the tax so the on-chain slippage check passes correctly.
684
+
685
+ #### `RouteAllocationPlan`
686
+
687
+ | Field | Type | Description |
688
+ |---|---|---|
689
+ | `amountIn` | `string` | Total input in wei |
690
+ | `totalAmountOut` | `string` | Total expected output in wei |
691
+ | `routes` | `SingleRouteAllocation[]` | Array of split routes |
692
+
693
+ #### `SingleRouteAllocation`
694
+
695
+ | Field | Type | Description |
696
+ |---|---|---|
697
+ | `amountIn` | `string` | Input amount for this route portion |
698
+ | `hops` | `HopAllocationPlan[]` | Sequential hops in this route |
699
+
700
+ #### `HopAllocationPlan`
701
+
702
+ | Field | Type | Description |
703
+ |---|---|---|
704
+ | `tokenIn` | `string` | Hop input token |
705
+ | `tokenOut` | `string` | Hop output token |
706
+ | `totalAmountOut` | `string` | Total output from all legs in this hop |
707
+ | `legs` | `HopAdapterAllocationPlan[]` | Adapter legs executing this hop (can be split across DEXes) |
708
+
709
+ #### `HopAdapterAllocationPlan`
710
+
711
+ | Field | Type | Description |
712
+ |---|---|---|
713
+ | `adapter` | `string` | Human-readable DEX adapter name (e.g. `"PulseXV2"`) |
714
+ | `amountIn` | `string` | Input amount routed through this adapter |
715
+ | `amountOut` | `string` | Expected output from this adapter |
716
+
717
+ ---
718
+
719
+ ## Error Handling
720
+
721
+ Errors are returned as JSON with an `error` field:
722
+
723
+ ```json
724
+ { "error": "Missing required parameters: from, to, amount" }
725
+ ```
726
+
727
+ ### HTTP Status Codes
728
+
729
+ | HTTP Status | Meaning |
730
+ |---|---|
731
+ | `200` | Successful response |
732
+ | `400` | Invalid parameters (missing, malformed, out of range) |
733
+ | `401` | Missing API key |
734
+ | `403` | Invalid API key |
735
+ | `429` | Rate limit exceeded (per-key total **or** per-IP sub-limit) |
736
+ | `502` | Backend routing failure |
737
+
738
+ ### Quote Errors (`/swap/quote`)
739
+
740
+ | Error | Cause |
741
+ |---|---|
742
+ | `"Missing required parameter: network"` | `network` query param absent |
743
+ | `"This network is not supported at this time."` | `network` is not `"pulsechain"` |
744
+ | `"Missing required parameters: from, to, amount"` | One or more required query params absent |
745
+ | `"Invalid from address (must be 0x + 40 hex chars)"` | `from` is not a valid hex address |
746
+ | `"Invalid to address (must be 0x + 40 hex chars)"` | `to` is not a valid hex address |
747
+ | `"Cannot swap token to itself"` | `from` and `to` are the same address |
748
+ | `"Amount must be a valid integer string"` | `amount` is not a parseable integer |
749
+ | `"Amount must be greater than 0"` | `amount` is zero or negative |
750
+ | `"Amount exceeds maximum allowed value"` | `amount` exceeds 10²⁷ (1 billion tokens @ 18 decimals) |
751
+ | `"Slippage must be a number between 0 and 5000 (basis points)"` | `slippage` out of range |
752
+ | `"Fee must be a number between 30 and 100 (basis points). Minimum fee is 0.30%."` | `fee` out of range or below minimum |
753
+ | `"receiver must be a valid Ethereum address (0x + 40 hex chars)"` | Malformed receiver address |
754
+ | `"partnerAddress must be a valid Ethereum address (0x + 40 hex chars)"` | Malformed partner address |
755
+ | `"Failed to find route"` | No viable swap path exists, or route computation timed out (30s limit) |
756
+
757
+ ### Tax Check Errors (`/swap/checkTax`)
758
+
759
+ | Error | Cause |
760
+ |---|---|
761
+ | `"Invalid or missing token address (must be 0x + 40 hex chars)"` | Missing or malformed `token` param |
762
+ | `"This network is not supported at this time."` | Unsupported `network` value |
763
+
764
+ ### On-Chain Revert Errors
765
+
766
+ If the swap transaction reverts on-chain, the SwitchRouter contract returns one of these custom errors:
767
+
768
+ | Error | Meaning |
769
+ |---|---|
770
+ | `FinalAmountOutTooLow()` | Output after fees fell below `_minTotalAmountOut` — price moved beyond your slippage tolerance. Retry with a fresh quote or increase slippage. |
771
+ | `ExcessiveFee()` | `_fee` exceeds the contract maximum (100 bps / 1 %). |
772
+ | `InsufficientFee()` | `_fee` is below the protocol's `MIN_FEE`. Contact the Switch team if you need a lower fee. |
773
+ | `MsgValueMismatch()` | For native PLS swaps, `msg.value` must exactly equal the route's total `amountIn`. |
774
+ | `ZeroInput()` | No input amount was provided. |
775
+
776
+ ---
777
+
778
+ ## Partner Fee Sharing
779
+
780
+ Switch supports a **50/50 fee-sharing** model for integration partners:
781
+
782
+ 1. Set a `fee` (e.g. `30` = 0.30 %, minimum `30` = 0.30 %) and your `partnerAddress` when calling the API.
783
+ 2. The SwitchRouter contract automatically splits collected fees **during the swap**:
784
+ - **50 %** sent to the protocol
785
+ - **50 %** sent directly to your `partnerAddress`
786
+
787
+ No claiming step is required — your share arrives in the same transaction as the swap.
788
+
789
+ > If `partnerAddress` is omitted or `0x0`, the fee accrues entirely to the protocol.
790
+
791
+ ### Fee on Input vs Fee on Output
792
+
793
+ Every quote response includes **two** transaction objects (when `sender` is provided):
794
+
795
+ | Field | `feeOnOutput` | How it works | User experience |
796
+ |---|---|---|---|
797
+ | `tx` (default) | `false` | Fee is deducted from the input amount *before* routing. The user sends the full `amount` but less gets routed through DEX pools. | User sends exactly `amount` tokens. Output is slightly lower because less was routed. |
798
+ | `txFeeOnOutput` | `true` | Full input is routed through DEX pools, then the fee is deducted from the output *before* delivery. | User sends exactly `amount` tokens. The gross output is higher but the fee is taken from it. |
799
+
800
+ Both modes produce similar net results. **Fee on input** (`tx`) is the default and most common choice. **Fee on output** (`txFeeOnOutput`) can be preferable when you want to collect fees in the output token.
801
+
802
+ Since every response contains both variants, you can **decide which to use at send time** — no need to re-fetch the quote.
803
+
804
+ > **Note:** The `minAmountOut` in the API response already accounts for the fee in both modes, so the on-chain slippage check will pass correctly regardless of which variant you send.
805
+
806
+ ### Choosing the Right Mode
807
+
808
+ The best fee mode depends on your use case. The example below shows a **combination strategy** that prioritizes avoiding tax tokens, collecting PLS/WPLS, and favoring blue-chip tokens — but you can create your own strategy tailored to your specific needs:
809
+
810
+ ```ts
811
+ import { NATIVE_PLS, WPLS, BLUE_CHIPS } from "@switch-win/sdk/constants";
812
+ import type { BestPathResponse } from "@switch-win/sdk/types";
813
+
814
+ const MY_PROJECT_TOKEN = "0xYourProjectTokenAddress".toLowerCase();
815
+ const plsAddresses = [NATIVE_PLS.toLowerCase(), WPLS.toLowerCase()];
816
+
817
+ function shouldFeeOnOutput(from: string, to: string, quote?: BestPathResponse): boolean {
818
+ const fromAddr = from.toLowerCase();
819
+ const toAddr = to.toLowerCase();
820
+
821
+ // Priority 1: If either token is your project token, take fee in the opposite
822
+ if (fromAddr === MY_PROJECT_TOKEN) return true; // fee on output = collect output
823
+ if (toAddr === MY_PROJECT_TOKEN) return false; // fee on input = collect input
824
+
825
+ // Priority 2: Avoid collecting tax tokens (fee revenue lost to transfer tax)
826
+ if (quote) {
827
+ const fromIsTax = quote.fromTokenTax?.isTaxToken ?? false;
828
+ const toIsTax = quote.toTokenTax?.isTaxToken ?? false;
829
+ if (fromIsTax && !toIsTax) return true; // collect non-tax output
830
+ if (toIsTax && !fromIsTax) return false; // collect non-tax input
831
+ }
832
+
833
+ // Priority 3: Prefer collecting PLS/WPLS
834
+ if (plsAddresses.includes(fromAddr)) return false; // fee on input = collect PLS
835
+ if (plsAddresses.includes(toAddr)) return true; // fee on output = collect PLS
836
+
837
+ // Priority 4: Prefer collecting blue chips
838
+ if (BLUE_CHIPS.has(toAddr) && !BLUE_CHIPS.has(fromAddr)) return true;
839
+ if (BLUE_CHIPS.has(fromAddr) && !BLUE_CHIPS.has(toAddr)) return false;
840
+
841
+ // Default: fee on input
842
+ return false;
843
+ }
844
+ ```
845
+
846
+ Then send the chosen transaction:
847
+
848
+ ```ts
849
+ const useFeeOnOutput = shouldFeeOnOutput(from, to, quote);
850
+ const chosenTx = useFeeOnOutput ? quote.txFeeOnOutput! : quote.tx!;
851
+ await signer.sendTransaction(chosenTx);
852
+ ```
853
+
854
+ > **Tip:** You can choose differently on every swap — adapt dynamically based on the token pair. The strategy above covers the most common scenarios (project tokens, tax avoidance, PLS preference, blue chips), but feel free to adjust the priority order, add project-specific logic, or implement a completely different approach that suits your integration best.
855
+
856
+ ---
857
+
858
+ ## Limit Orders
859
+
860
+ Switch Limit Orders let users place **gasless, signed orders** that are filled automatically when market conditions are met — no gas to create, no token deposits, EIP-712 signed.
861
+
862
+ **→ Full integration guide: [`LIMIT-ORDERS.md`](LIMIT-ORDERS.md)**
863
+
864
+ Covers: creating orders, approvals, `feeOnOutput` decision guide (tax tokens & operator flexibility), querying, cancellation, API reference, types, and EIP-712 details.
865
+
866
+ ---
867
+
868
+ ## Constants & Addresses
869
+
870
+ All constants are importable from [`src/constants.ts`](src/constants.ts).
871
+
872
+ | Name | Value |
873
+ |---|---|
874
+ | **Chain** | PulseChain (Chain ID `369`) |
875
+ | **SwitchRouter** | `0x31077B259e7fEEB7bE39bF298274BaE94Ee57B7a` |
876
+ | **SwitchLimitOrder** | `0x28754379e9E9867A64b77437930cBc5009939692` |
877
+ | **SwitchPLSFlow** | `0x0fD3fD40F06159606165F21047B83136172273E3` |
878
+ | **Native PLS sentinel** | `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` |
879
+ | **WPLS** | `0xA1077a294dDE1B09bB078844df40758a5D0f9a27` |
880
+ | **Fee denominator** | `10000` (basis points) |
881
+ | **Max slippage** | `5000` bps (50 %) |
882
+ | **Max fee** | `100` bps (1 %) |
883
+ | **Min fee** | `30` bps (0.30 %) — enforced on-chain by `MIN_FEE` |
884
+ | **Default slippage** | `50` bps (0.50 %) |
885
+ | **Swap API base** | `https://quote.switch.win` |
886
+ | **Limit Order API base** | `https://api.switch.win` |
887
+
888
+ ---
889
+
890
+ ## Full Integration Examples
891
+
892
+ Complete runnable examples are in the [`examples/`](examples/) directory:
893
+
894
+ | File | Language | Description |
895
+ |---|---|---|
896
+ | [`swap-ethers.ts`](examples/swap-ethers.ts) | TypeScript | Full 5-step swap flow with ethers.js v6 (adapters → checkTax → quote → approve → send) |
897
+ | [`swap-web3py.py`](examples/swap-web3py.py) | Python | Full 5-step swap flow with web3.py |
898
+ | [`limit-order-ethers.ts`](examples/limit-order-ethers.ts) | TypeScript | Full limit order lifecycle (build → approve → sign → submit → query → cancel) |
899
+ | [`nextjs-proxy.ts`](examples/nextjs-proxy.ts) | TypeScript | Next.js catch-all proxy for `/swap/*` endpoints (keeps API key server-side) |
900
+ | [`react-hooks.tsx`](examples/react-hooks.tsx) | React/TSX | `useAdapters`, `useCheckTax`, `useSwapQuote` hooks + example `SwapCard` component |
901
+
902
+ ---
903
+
904
+ ## Rate Limits
905
+
906
+ All requests require a valid API key. The API enforces **dual-bucket rate limiting** — both checks must pass:
907
+
908
+ | Bucket | Scope | Default Limit | Description |
909
+ |---|---|---|---|
910
+ | Per-key | All IPs sharing one API key | 100 req/min | Total throughput for the key (configurable per key) |
911
+ | Per-IP | Each unique IP within a key | 10 req/min | Prevents one caller from monopolising a shared key |
912
+
913
+ Both limits apply simultaneously. A single IP can never exceed 10/min, and all IPs combined can never push a key past its total limit.
914
+
915
+ Every successful response includes informational headers:
916
+
917
+ ```
918
+ X-RateLimit-Limit: 100
919
+ X-RateLimit-Remaining: 87
920
+ ```
921
+
922
+ ### IP Whitelisting for Backend Servers
923
+
924
+ If your backend proxies Switch quotes to end users (server-to-server calls), ask the Switch team to **whitelist your server IP(s)** for your API key. Whitelisted IPs skip the 10/min per-IP sub-limit but still count toward the key's total — giving your backend access to the key's full allocation.
925
+
926
+ If you receive a `429 Too Many Requests` response, back off and retry after a short delay using exponential backoff.
927
+
928
+ To request a new API key, IP whitelisting, or rate limit changes, contact the Switch team (see [Support](#support)).
929
+
930
+ ---
931
+
932
+ ## Support
933
+
934
+ For API key requests, integration help, or feature requests, contact the Switch team:
935
+
936
+ - **Website:** [switch.win](https://switch.win)
937
+ - **Telegram:**
938
+ - [@BrandonDavisR2R](https://t.me/BrandonDavisR2R)
939
+ - [@bttscott](https://t.me/bttscott)
940
+ - [@shanebtt](https://t.me/shanebtt)
941
+
942
+ ---
943
+
944
+ ## License
945
+
946
+ MIT
947
+
948
+ ---
949
+
950
+ *Last updated: February 2026*