@switch-win/sdk 1.2.9 → 1.3.1

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/GASLESS.md +434 -0
  3. package/LIMIT-ORDERS.md +213 -205
  4. package/README.md +134 -105
  5. package/ROBINHOOD.md +201 -198
  6. package/abi/GaslessSwitchV2ABI.json +1095 -0
  7. package/dist/constants.d.ts +276 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +403 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/gasless-client.d.ts +82 -0
  12. package/dist/gasless-client.d.ts.map +1 -0
  13. package/dist/gasless-client.js +467 -0
  14. package/dist/gasless-client.js.map +1 -0
  15. package/dist/gasless-index.d.ts +5 -0
  16. package/dist/gasless-index.d.ts.map +1 -0
  17. package/dist/gasless-index.js +5 -0
  18. package/dist/gasless-index.js.map +1 -0
  19. package/dist/gasless-operators.d.ts +32 -0
  20. package/dist/gasless-operators.d.ts.map +1 -0
  21. package/dist/gasless-operators.js +132 -0
  22. package/dist/gasless-operators.js.map +1 -0
  23. package/dist/gasless-store.d.ts +18 -0
  24. package/dist/gasless-store.d.ts.map +1 -0
  25. package/dist/gasless-store.js +83 -0
  26. package/dist/gasless-store.js.map +1 -0
  27. package/dist/gasless-types.d.ts +222 -0
  28. package/dist/gasless-types.d.ts.map +1 -0
  29. package/dist/gasless-types.js +2 -0
  30. package/dist/gasless-types.js.map +1 -0
  31. package/dist/gasless.d.ts +135 -0
  32. package/dist/gasless.d.ts.map +1 -0
  33. package/dist/gasless.js +513 -0
  34. package/dist/gasless.js.map +1 -0
  35. package/dist/index.d.ts +29 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +30 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/limit-orders.d.ts +440 -0
  40. package/dist/limit-orders.d.ts.map +1 -0
  41. package/dist/limit-orders.js +387 -0
  42. package/dist/limit-orders.js.map +1 -0
  43. package/dist/networks/index.d.ts +3 -0
  44. package/dist/networks/index.d.ts.map +1 -0
  45. package/dist/networks/index.js +2 -0
  46. package/dist/networks/index.js.map +1 -0
  47. package/dist/networks/robinhood.d.ts +536 -0
  48. package/dist/networks/robinhood.d.ts.map +1 -0
  49. package/dist/networks/robinhood.js +489 -0
  50. package/dist/networks/robinhood.js.map +1 -0
  51. package/dist/types.d.ts +436 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +19 -0
  54. package/dist/types.js.map +1 -0
  55. package/examples/gasless-nextjs-proxy.ts +87 -0
  56. package/examples/gasless-operator.ts +76 -0
  57. package/examples/gasless-swap.ts +173 -0
  58. package/examples/limit-order-ethers.ts +195 -0
  59. package/examples/nextjs-proxy.ts +82 -0
  60. package/examples/react-hooks.tsx +396 -0
  61. package/examples/swap-ethers.ts +209 -0
  62. package/examples/swap-web3py.py +267 -0
  63. package/package.json +53 -12
  64. package/src/constants.ts +184 -182
  65. package/src/gasless-client.ts +824 -0
  66. package/src/gasless-index.ts +4 -0
  67. package/src/gasless-operators.ts +227 -0
  68. package/src/gasless-store.ts +137 -0
  69. package/src/gasless-types.ts +262 -0
  70. package/src/gasless.ts +756 -0
  71. package/src/index.ts +69 -63
  72. package/src/limit-orders.ts +206 -195
  73. package/src/types.ts +80 -55
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # 1.3.1 - 2026-11-11
2
+
3
+ - Clarify that frontends display expected gasless output as the main amount,
4
+ show the slippage-adjusted minimum separately, and sign that exact minimum.
5
+ Add field/editor guidance and update the browser example callback comments.
6
+ - Refresh the release documentation to reflect the published 1.3.0 helpers.
7
+ Runtime behavior, fee policy and signed order construction are unchanged.
8
+
9
+ # 1.3.0 - 2026-11-11
10
+
11
+ GaslessSwitch V2 support.
12
+
13
+ - Clarify the mandatory fixed 0.1% gasless protocol fee and the separate capped
14
+ operator payout. Document regular-only integrations for partners who do not
15
+ want to offer that fee policy, including recovery of existing pending orders.
16
+ - Type the November 10 API eligibility summary, native gas/payout economics,
17
+ conservative safety margins, funding status and rejection reasons. Existing
18
+ preview eligibility checks honor the stricter live server recommendation.
19
+ - Expose that summary on both `GaslessQuoteDecision` branches, preserve API
20
+ rejection reasons through `SERVER_INELIGIBLE`, reject stale/contradictory
21
+ metadata, and retain the conservative reserve when current gas rises.
22
+ - Add dedicated PulseChain/Robinhood deployment and EIP-712 validation, full V2
23
+ ABI, tax/fee-side selection, bound settlement previews, and conservative
24
+ operator gas eligibility checks.
25
+ - Add refreshed single-click signing/submission, boosted approvals with an
26
+ optional exact amount, atomic browser pending storage, transport recovery,
27
+ chain-confirmed expiry handling, and settlement transaction links.
28
+ - Add operator contract classification, priority selection, payout/gas math,
29
+ registration checks and validated fill encoding. Gasless uses a separate
30
+ 5 bps total-payout policy; ordinary limit orders retain their own policy.
31
+ - Add browser, server proxy, and operator examples and a dedicated integration
32
+ guide. Examples are typechecked; tests are offline and never broadcast.
33
+ - Build ESM runtime files and declarations and point package exports to the
34
+ built files, including the new gasless entry points and ABI.
package/GASLESS.md ADDED
@@ -0,0 +1,434 @@
1
+ # GaslessSwitch V2 integration
2
+
3
+ GaslessSwitch V2 helpers are published in `@switch-win/sdk` version **1.3.0**.
4
+
5
+ Gasless swaps use a dedicated settlement contract. A user approves that
6
+ contract, signs an EIP-712 order, and submits the signature to the order API.
7
+ An approved operator pays settlement gas. Approval, wrapping, and on-chain
8
+ cancellation still require native gas. A quote or an accepted order is not a
9
+ guarantee of a fill.
10
+
11
+ ## Fixed gasless fee
12
+
13
+ **Every GaslessSwitch settlement charges a fixed 0.1% protocol fee (10 bps).**
14
+ This is enforced by the settlement contract. Integrators cannot customize,
15
+ reduce, or waive it through SDK options, the quote API's `fee` parameter,
16
+ `partnerAddress`, or regular Router fee settings. Partner fee sharing allocates
17
+ the collected protocol fee; it does not change the user's fee rate.
18
+
19
+ The operator payout is separate and capped at **0.05% of the after-fee base**,
20
+ including settlement gas and the operator incentive. It may be lower than the
21
+ cap. Request `gasless=true&fee=10`: the checked preview already reserves the
22
+ operator payout. Display its expected/minimum output without subtracting the
23
+ protocol fee or operator payout a second time. Approval and wrapping gas,
24
+ token transfer taxes, and DEX fees can also apply.
25
+
26
+ **If your integration does not want to offer this fixed gasless fee policy,
27
+ offer regular swaps only.** Regular swaps use the Router's applicable fee
28
+ policy and on-chain minimum; display the selected quote's `protocolFeeBps`.
29
+
30
+ ## Regular-only integration
31
+
32
+ 1. Show only the regular swap action in your frontend. Leave out the Gasless
33
+ selector and `GaslessSwapClient` quote, approval, signing, and submission flow.
34
+ 2. Request `/swap/quote` with `gasless` omitted (or `gasless=false`) and the
35
+ integration's regular `fee` setting, subject to the Router's fee minimum.
36
+ 3. Follow the [regular swap integration flow](README.md#swap-integration-flow):
37
+ approve the regular Router when needed, then submit the regular quote's `tx`
38
+ from the user's wallet. The user pays network gas for that transaction.
39
+
40
+ The `gaslessEligibility` field on an ordinary quote is informational
41
+ (`not_checked`); receiving that field does not opt the integration into gasless.
42
+ If removing Gasless from an existing integration, retain status/recovery for
43
+ already signed pending orders until they are settled, cancelled, or confirmed
44
+ expired before allowing another trade against that wallet's pending lock.
45
+
46
+ ## Deployments and fee policy
47
+
48
+ Verified against the public config endpoints on November 9, 2026:
49
+
50
+ | Network | Chain | GaslessSwitch V2 |
51
+ | --- | ---: | --- |
52
+ | PulseChain | 369 | `0xB5bd12E009D625d8b122d0Ff9EdeD8bA2cEdeaeB` |
53
+ | Robinhood | 4663 | `0xc39cB531f71059fd427a63bF8a2f4b822D6B9b02` |
54
+
55
+ | Property | Gasless swaps | Ordinary limit orders |
56
+ | --- | --- | --- |
57
+ | EIP-712 name / version | `GaslessSwitch` / `2` | `SwitchLimitOrder` / `2` |
58
+ | Protocol fee | Fixed 10 bps (0.1%); not configurable by integrators | 30 bps (0.3%) |
59
+ | Contract payout cap | 5 bps (0.05%) of realized after-fee base | Separate 500 bps protection |
60
+ | Gas reimbursement | Included in the 5 bps payout | Operator's own profitability policy |
61
+ | Maker approval | GaslessSwitch in both fee modes | Depends on signed fee mode |
62
+ | Route/direct fills | Route fills only | Both, subject to contract protection |
63
+ | SDK expiry | 180 seconds | User-selected |
64
+
65
+ The cap is **0.05% of the after-fee token base**, not 0.05% of the excess and
66
+ not a 0.5% minimum profit target. Slippage never increases it. The registered
67
+ operator role comes through the existing operator-access administration.
68
+ `readGaslessChainState` also checks pause status, the immutable Router and
69
+ wrapped-native addresses, domain separator, operator gate, and Router fee
70
+ exemption. No extra regular Router fee is added to settlement.
71
+
72
+ Fetch `GET /limit-orders/config?network=pulsechain` or `network=robinhood`.
73
+ Validate the `gasless*` fields against the trusted deployment. Do not sign or
74
+ approve an address simply because an API returned it. The SDK pins V2;
75
+ legacy gasless V1 contracts remain historical/drain targets. A deliberate
76
+ migration can use the client's `deployment` override after verifying it.
77
+
78
+ ## Frontend flow
79
+
80
+ 1. Connect the actual maker wallet and verify its chain. Keep amounts as raw
81
+ decimal integer strings; never convert a token amount through `Number`.
82
+ 2. Check both tokens' directional taxes. Input sell tax prefers output fees;
83
+ output buy tax, including both-tax pairs, prefers input fees. Otherwise use
84
+ the same known-token fee priority as the regular Switch frontend.
85
+ 3. Request `gasless=true`, `fee=10`, the selected `feeOnOutput`, actual maker,
86
+ recipient, partner, amount, and user slippage. Leave adapter filters unset.
87
+ 4. Require the settlement preview's `eligible` flag and conservative server
88
+ recommendation. Default to Gasless when eligible; explain why Regular is
89
+ required when it is not. Unknown is not eligible. Do not silently send a
90
+ Regular transaction in response to a Gasless signing click.
91
+ 5. Check maker balance and allowance to GaslessSwitch. Default the unlimited
92
+ approval toggle on, allow users to turn it off, and explain the spender and
93
+ approval cost. Wait for successful confirmation before signing.
94
+ 6. A Sign click owns refresh, visible quote update, signing, and POST. Freeze
95
+ the trade intent while preparing/signing; abort on account, chain, pair,
96
+ amount, recipient, partner, or slippage changes. Update the main expected
97
+ output and the separate minimum received before the wallet prompt. The order
98
+ signs the refreshed minimum, while the main amount stays on expected output.
99
+ 7. Persist the pending order before opening the wallet. Persist the signature
100
+ before POST. Keep the pending lock on uncertain submission. Never generate
101
+ a replacement nonce merely because a request timed out. Block Regular and
102
+ Wrap actions against the same wallet's trade while that lock remains.
103
+ 8. Poll order status, not quotes. After a fill, show the settlement link and
104
+ leave quotes paused until the user starts another swap or edits the form.
105
+
106
+ Use an ethers v6 `Provider` / `Signer` with the client. The standalone signing
107
+ params can also be passed to ethers v5 `_signTypedData`.
108
+
109
+ ```ts
110
+ import { BrowserProvider } from "ethers";
111
+ import { GaslessSwapClient, createGaslessBrowserStore } from "@switch-win/sdk/gasless";
112
+
113
+ const provider = new BrowserProvider(window.ethereum, "any");
114
+ const signer = await provider.getSigner();
115
+ const maker = await signer.getAddress();
116
+ const client = new GaslessSwapClient({
117
+ network: "pulsechain",
118
+ provider,
119
+ quoteEndpoint: "/api/switch/swap/quote",
120
+ taxEndpoint: "/api/switch/swap/checkTax",
121
+ ordersEndpoint: "/api/switch/limit-orders",
122
+ });
123
+ const store = createGaslessBrowserStore("pulsechain", maker);
124
+ const trade = {
125
+ maker,
126
+ tokenIn: "0x15d38573d2feeb82e7ad5187ab8c1d52810b1f07",
127
+ tokenOut: "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39",
128
+ amountIn: "100000000", // 100 USDC (6 decimals)
129
+ slippageBps: 50,
130
+ };
131
+ const decision = await client.quote(trade);
132
+ if (!decision.eligible) {
133
+ showRegularOption(decision.reason);
134
+ } else {
135
+ displayQuote(decision.prepared);
136
+ // Later, on the user's click, after any required approval:
137
+ await client.signAndSubmit({
138
+ trade, signer, store, displayed: decision.prepared,
139
+ onPrepared: displayQuote, // Update main expected output and separate minimum before the prompt.
140
+ onPending: displayOrderProgress,
141
+ });
142
+ }
143
+ ```
144
+
145
+ The complete [browser flow example](examples/gasless-swap.ts) handles approvals,
146
+ request cancellation, intent changes, pending recovery, and quote suspension.
147
+ Its UI callbacks are integration points, not a new design system. Reuse your
148
+ swap confirmation modal. Display these stages accurately:
149
+
150
+ - Wallet signature requested / signed.
151
+ - Order accepted by the API (a request in progress is not accepted).
152
+ - Waiting for an operator; show the fixed deadline countdown with tabular
153
+ numerals and fixed width so the layout does not move.
154
+ - Confirmed settlement with a transaction link, or cancellation/chain expiry.
155
+
156
+ ## What makes a quote eligible
157
+
158
+ The current API provides this through `gaslessExecution` and the additive
159
+ `gaslessEligibility` summary (live on both APIs November 10, 2026).
160
+ It replays GaslessSwitch V2 against
161
+ the quote's pinned block, measures actual recipient and operator balance
162
+ deltas, estimates settlement gas, and values the payout through a native-token
163
+ conversion route. Simulation uses ephemeral maker funding/signature/allowance
164
+ stand-ins: it does **not** prove the real maker's balance, approval, or
165
+ signature. The client checks those separately before submission.
166
+
167
+ The server now discounts the native payout valuation by 10% and requires that
168
+ stressed payout to exceed twice its already-padded gas budget. It also checks
169
+ that a registered operator has real native funds covering that conservative
170
+ reserve, and requires at least 50 bps (0.5%) of user-selected slippage. It never
171
+ raises slippage silently or increases the 5 bps payout cap. These are server
172
+ recommendation rules in addition to the client's independent checks below.
173
+
174
+ Read `gaslessEligibility.economics` for formatted PLS/ETH payout, estimated gas,
175
+ padded budget, conservative reserve, required payout and shortfall. Raw values
176
+ use decimal wei strings; `netAfterGasBudgetNativeWei` can be negative.
177
+ `checkedAt` and `expiresAt` are milliseconds. The same summary appears under
178
+ `gaslessExecution.eligibility`. `reasonCode` and `reason` explain rejection.
179
+ Normal quotes report `status: "not_checked"` without settlement simulation.
180
+ A `gaslessExecution.status` of `ready` only means simulation completed; it
181
+ does not imply `eligible: true`. Require the checked flag and exact binding.
182
+
183
+ `operatorLivenessVerified` is explicitly false: registration and native funds
184
+ do not prove that a worker is online. Integrators must not turn this estimate
185
+ into a fill guarantee. Borderline swaps now recommend Regular more often.
186
+
187
+ The SDK requires all of the following:
188
+
189
+ - A ready V2 preview matching chain, contract, maker, normalized tokens, full
190
+ input, recipient, native unwrap flag, fee side, payout token, slippage, quote
191
+ block, and `keccak256(selectedTx.data)`.
192
+ - Decodable `goSwitch` route bytes targeting the trusted Router, with the
193
+ expected fee, recipient and partner, connected route endpoints, and zero
194
+ native value. These bytes identify the preview route; they are **never**
195
+ sent as a maker transaction for a gasless swap.
196
+ - Preview and current gas checks no older than 30 seconds, a recent quote
197
+ block, and the verified current on-chain settlement policy.
198
+ - `gasLimit >= ceil(estimatedGas * 1.30)`.
199
+ - Gas price at least the maximum of the API quote's price, preview price,
200
+ and `ceil(currentRawGasPrice * 1.30)`.
201
+ - Budget at least `ceil(gasLimit * gasPrice * 1.10)` and actual payout valued
202
+ in native currency **strictly greater** than that budget.
203
+ - Nominal payout within the 5 bps cap, measured payout no greater than nominal,
204
+ and actual recipient output at least the signed minimum.
205
+
206
+ No flat USD threshold or fixed gas-unit guess can replace this. Route length,
207
+ token transfers, tax hooks, payout liquidity, and gas price can make a larger
208
+ swap unsuitable too. Rejection codes distinguish stale checks, missing
209
+ previews, changed configuration, unknown taxes, and insufficient operator gas.
210
+ An unavailable check may be retried with backoff; insufficient payout normally
211
+ requires a larger amount or Regular. Never loosen the cap to make it pass.
212
+
213
+ These checks improve the execution estimate. Prices, gas, token rules, and
214
+ operator availability can still change during the three-minute order window;
215
+ an operator independently simulates the signed order before filling it.
216
+
217
+ ## API eligibility response
218
+
219
+ `GET /swap/quote` returns `gaslessEligibility` on every successful quote.
220
+ An ordinary quote returns `not_checked`; it does not run a settlement check.
221
+ To request a check, include `network`, `gasless=true`, `fee=10`, the selected
222
+ `feeOnOutput`, `from`, `to`, raw `amount`, actual maker as `sender`, recipient
223
+ as `receiver`, and user slippage in **basis points** (`slippage=50` means 0.5%).
224
+ Keep API credentials on your server. `GaslessSwapClient.quote()` already sends
225
+ these fields, checks directional taxes/fee mode, and validates the response.
226
+ The 5 bps payout is reserved by the preview: do not send `fee=15` for gasless.
227
+
228
+ The response is also present as `gaslessExecution.eligibility`. Public types
229
+ `GaslessEligibility` and `GaslessEligibilityReason` are exported from the root
230
+ SDK and `@switch-win/sdk/gasless`. The client exposes the summary as
231
+ `decision.eligibility` on both branches, including rejected quotes:
232
+
233
+ ```ts
234
+ const decision = await client.quote(trade);
235
+ const check = decision.eligibility;
236
+ if (check?.economics) {
237
+ showGasCosts({
238
+ currency: check.nativeSymbol,
239
+ payout: check.economics.operatorPayoutNative,
240
+ estimatedGas: check.economics.estimatedGasCostNative,
241
+ conservativeReserve: check.economics.conservativeGasBudgetNative,
242
+ requiredPayout: check.economics.requiredOperatorPayoutNative,
243
+ });
244
+ }
245
+ if (!decision.eligible) {
246
+ showRegularOption(decision.reason);
247
+ // SERVER_INELIGIBLE preserves check.reasonCode, check.reason and economics.
248
+ // Offer a deliberate retry with backoff only when decision.retryable is true.
249
+ } else {
250
+ displayQuote(decision.prepared);
251
+ // Use signAndSubmit on the user's click; it rechecks/refreshes before signing.
252
+ }
253
+ ```
254
+
255
+ The formatted amounts are exact native decimal strings (PLS or ETH), not USD.
256
+ Raw `*Wei` fields are integer strings; keep them as strings/BigInt. The net
257
+ `netAfterGasBudgetNativeWei` is signed and can be negative. Economics/operator
258
+ details may be absent when verification was unavailable; display unknown
259
+ rather than inventing zero gas costs. `checkedAt`/`expiresAt` are milliseconds.
260
+
261
+ | `reasonCode` | Meaning and frontend action |
262
+ | --- | --- |
263
+ | `READY` | Server check passed; still require the SDK's complete `decision.eligible` check. |
264
+ | `NOT_REQUESTED` | Request a gasless quote with the required parameters. |
265
+ | `GAS_NOT_COVERED` | The capped payout does not cover padded gas; increase amount or use Regular. |
266
+ | `INSUFFICIENT_SAFETY_MARGIN` | Covers basic gas but fails the conservative reserve; increase amount or use Regular. |
267
+ | `OPERATOR_GAS_UNAVAILABLE` | Verified operator lacks the reserved native gas funds; use Regular or retry later. |
268
+ | `NO_APPROVED_OPERATOR` | No configured approved operator could be verified; use Regular or retry later. |
269
+ | `SLIPPAGE_BUFFER_TOO_SMALL` | User tolerance is below 50 bps; offer Regular or an explicit user change. |
270
+ | `STALE_PREVIEW` | Refresh the quote before signing. |
271
+ | `EXECUTION_UNVERIFIED` / `PAYOUT_UNVALUED` | Simulation/native valuation could not be trusted; offer Regular or a bounded retry. |
272
+
273
+ The client rejects stale, contradictory or mismatched summary/preview fields.
274
+ For policy-v1 metadata it also reapplies the 2x reserve and 10% payout haircut
275
+ using freshly checked gas prices, so a server `READY` can become a local
276
+ rejection before signing. Always use `decision.eligible` to gate the action;
277
+ `decision.eligibility` describes the original server check, not a guarantee.
278
+ Older V2 endpoints without this additive metadata retain the existing bound
279
+ preview/fee/cap/gas checks and expose no `decision.eligibility` summary.
280
+
281
+ Treat `ineligible`, `unavailable`, and `not_checked` as unavailable for signing.
282
+ Never automatically raise slippage or the operator cap. Offer a separate
283
+ Regular quote with the integration's Regular fee policy when selected. Refresh
284
+ after a trade edit or approval/signing delay, then freeze the displayed minimum
285
+ before signing. Once signed, poll that order's status rather than requesting
286
+ new quotes. `operatorLivenessVerified: false` means the API does not prove a
287
+ worker is online, even when an approved wallet has sufficient native funds.
288
+
289
+ ## Amounts, fees, taxes, and slippage
290
+
291
+ ### Expected output versus signed minimum
292
+
293
+ Use the same main output metric for Gasless and Regular: **expected output
294
+ after costs, before the user's slippage allowance**.
295
+
296
+ | SDK field | Frontend / signing use |
297
+ | --- | --- |
298
+ | `prepared.expectedOutput` | Main "Get" or "Expected output" amount, including the checked protocol fee, operator payout and token-tax effects. |
299
+ | `prepared.minAmountOut` | Separate "Minimum received" amount after slippage, and the exact `minAmountOut` used in the signed order. |
300
+
301
+ Keep the main field on `expectedOutput` while preparing and signing. Update
302
+ both amounts in `onPrepared` when a stale quote refreshes, before the wallet
303
+ prompt opens. Do not replace the main estimate with the lower minimum: that
304
+ makes Gasless look worse than a Regular quote displaying its expected output.
305
+
306
+ For example, a checked expected output of 1,000 tokens with 0.5% user slippage
307
+ shows **1,000** in the main field and **995 minimum received** separately.
308
+ The order signs **995**, so the operator can execute within that tolerance.
309
+
310
+ Both fields are raw integer strings in output-token units. Format them using
311
+ the token's decimals for display; never rebuild the signing minimum from a
312
+ rounded display value or use `expectedOutput` as the order's `minAmountOut`.
313
+ `signAndSubmit()` / `buildGaslessOrder()` already use the checked minimum.
314
+
315
+ ### Fee and tax calculations
316
+
317
+ The regular quote fields describe its ordinary Router call. For the gasless
318
+ UI use the checked `prepared.expectedOutput` and `prepared.minAmountOut`,
319
+ which come from actual V2 settlement simulation. Do not use the ordinary
320
+ quote's `minAmountOut`, or subtract another 0.05% from the preview.
321
+
322
+ Send `fee=10`, not `15`: the gasless preview reserves the operator payout
323
+ separately. Output-fee arithmetic for an untaxed unchanged route is roughly
324
+ `gross * 0.999 * 0.9995`; integer rounding and actual tax transfers matter.
325
+ For input fees the cap reduces the amount routed, and AMM price impact makes
326
+ an output-side percentage subtraction inaccurate.
327
+
328
+ For Regular fallback, obtain a separate Regular quote and display its actual
329
+ `protocolFeeBps`. The current Switch frontend requests 15 bps for Regular;
330
+ the current Router floor is 10 bps on both chains. Neither value changes
331
+ GaslessSwitch's fixed 10 bps settlement fee.
332
+
333
+ The signed minimum is `floor(previewExpected * (10000 - slippageBps) / 10000)`.
334
+ User slippage allows market movement. Better output belongs to the recipient;
335
+ it does not authorize a bigger operator payout. For an output-fee fill the
336
+ contract pays `min(afterFeeOutput - minimum, floor(afterFeeOutput * 5/10000))`.
337
+ For an input-fee fill it pays
338
+ `min(afterFeeInput - routedInput, floor(afterFeeInput * 5/10000))`, refunding
339
+ any additional unused input to the maker. A smaller surplus automatically
340
+ means a smaller payout, and it is fillable only if that payout still covers gas.
341
+
342
+ Tax-token support is conditional on successful simulation of that exact token,
343
+ route, maker and recipient. Do not ban every tax token, assume all adapters are
344
+ tax-safe, or apply the displayed tax twice. The preview measures settlement
345
+ transfers and payout transfer tax; unsupported/mutable tax behavior fails
346
+ closed. Ordinary LO route builders still need their tax-safe adapter rules.
347
+
348
+ ## Approvals and native currency
349
+
350
+ `client.approvalRequest()` builds an approval; `client.approve()` requests the
351
+ wallet transaction. Both fee modes approve **GaslessSwitch V2**, never the
352
+ Router or ordinary LimitOrder. Unlimited is the default; pass
353
+ `{ unlimited: false }` for an exact-amount approval. A token requiring zero
354
+ allowance first can use `{ resetToZero: true }`; confirm it, then build the
355
+ desired approval with a fresh gas price. Do not blindly zero every allowance.
356
+
357
+ The approval takes the maximum of freshly boosted RPC gas and the provided
358
+ `quoteGasPriceWei`. An API quote's gas price is already boosted and is not
359
+ multiplied again. Its gas limit is estimated for the actual approve call and
360
+ padded by 30%. Native balance and wallet policy can still prevent confirmation.
361
+
362
+ Raw PLS/ETH cannot be spent by an off-chain signature. Offer **Wrap and use
363
+ Gasless** or **Regular**, explain that wrapping requires gas, and wait for the
364
+ wrap receipt before a fresh approval/quote. Do not use PLSFlow for gasless
365
+ swaps. Native output is supported: the API receives the native sentinel, but
366
+ the signed tuple uses WPLS/WETH with `unwrapOutput=true`.
367
+
368
+ No Permit2 is used. Do not request Permit2 approvals or signatures.
369
+
370
+ ## Submission, recovery, and cancellation
371
+
372
+ POST the complete signed tuple plus `signature` and the explicit gasless
373
+ `limitOrderContract` to `/limit-orders?network=...`. The type remains
374
+ `LimitOrder`, but the EIP-712 domain is `GaslessSwitch`, version `2`, with the
375
+ gasless verifying contract. Ordinary LO signing helpers are not interchangeable.
376
+ The hosted API currently verifies EOA signatures; generic ERC-1271 maker
377
+ submission is not supported by this client even though the contract supports it.
378
+
379
+ `GaslessPendingStore` must be durable and atomically reserve a slot per maker
380
+ and network. The supplied browser store uses localStorage and Web Locks to
381
+ serialize tabs. If those APIs are unavailable, provide an equivalent atomic
382
+ application store. Do not fall back silently to volatile memory for a browser
383
+ integration. Corrupted or inaccessible storage must block a new order until
384
+ the previous signature's status is recovered.
385
+
386
+ On a lost POST response, `GaslessSubmissionError.pending` retains the original
387
+ signature and nonce. `client.retrySubmission(store)` first checks that exact
388
+ order, then can resend the original payload while sufficiently far from expiry.
389
+ The API returns an error on duplicate nonces; it is not an idempotent-success
390
+ endpoint. A record only counts as confirmation if its contract and **all**
391
+ signed fields match. Never change a signed minimum, fee side, recipient,
392
+ deadline or nonce during retry.
393
+
394
+ `client.reconcile(store)` makes a status request and, when needed, a chain
395
+ read. Use a non-overlapping loop at five-second intervals with backoff on
396
+ failure. `FILLED` includes `settlementTxUrl` when the indexer has the hash.
397
+ Local-clock expiry, API `EXPIRED`, a missing record, and transport failure do
398
+ not independently release the pending lock. A confirmed chain expiry can
399
+ release it even during an API outage. The chain must pass the signed
400
+ deadline, or an exact matching fill/cancellation must be observed. A closed
401
+ window is not proof that no fill occurred; check history for the final result.
402
+
403
+ Closing the modal does not cancel. For early cancellation call
404
+ `invalidateNonce(pending.order.nonce)` on `pending.contract` with the maker,
405
+ wait for confirmation, and reconcile. Cancellation costs native gas and can
406
+ race an operator's fill. Never use a DELETE request or the ordinary LO address.
407
+
408
+ ## Proxying and package checks
409
+
410
+ Keep API credentials server-side. The SDK accepts explicit quote, tax and order
411
+ endpoint overrides so browsers use their own proxy; see the
412
+ [restricted Next.js proxy example](examples/gasless-nextjs-proxy.ts). Apply
413
+ your hosting body limit, session/origin protections and rate limits. Forward
414
+ `gasless`, fee side, sender/receiver, partner and slippage unchanged. Avoid
415
+ overlapping quote searches: debounce edits, abort obsolete requests, and never
416
+ poll quotes while an order is pending or immediately after settlement.
417
+
418
+ This SDK revision builds ESM JavaScript and declarations into `dist`; package
419
+ exports use that output rather than unresolved TypeScript source files.
420
+ `npm pack` is local and does not publish. Run the tests before creating a local
421
+ tarball for an integration test:
422
+
423
+ ```sh
424
+ npm ci --ignore-scripts
425
+ npm run typecheck
426
+ npm test
427
+ npm pack
428
+ ```
429
+
430
+ Use a patched Node.js release for development and keep the lockfile. None of
431
+ the examples run a transaction on import. See the
432
+ [operator execution example](examples/gasless-operator.ts) and the dedicated
433
+ [operator repository](https://github.com/BuildTheTech/Switch-Operators-SDK)
434
+ (`GASLESS.md` in the companion local checkout).