@switch-win/sdk 1.2.8 → 1.3.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/CHANGELOG.md +26 -0
- package/GASLESS.md +407 -0
- package/LIMIT-ORDERS.md +212 -198
- package/README.md +131 -105
- package/ROBINHOOD.md +201 -198
- package/abi/GaslessSwitchV2ABI.json +1095 -0
- package/dist/constants.d.ts +276 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +403 -0
- package/dist/constants.js.map +1 -0
- package/dist/gasless-client.d.ts +81 -0
- package/dist/gasless-client.d.ts.map +1 -0
- package/dist/gasless-client.js +467 -0
- package/dist/gasless-client.js.map +1 -0
- package/dist/gasless-index.d.ts +5 -0
- package/dist/gasless-index.d.ts.map +1 -0
- package/dist/gasless-index.js +5 -0
- package/dist/gasless-index.js.map +1 -0
- package/dist/gasless-operators.d.ts +32 -0
- package/dist/gasless-operators.d.ts.map +1 -0
- package/dist/gasless-operators.js +132 -0
- package/dist/gasless-operators.js.map +1 -0
- package/dist/gasless-store.d.ts +18 -0
- package/dist/gasless-store.d.ts.map +1 -0
- package/dist/gasless-store.js +83 -0
- package/dist/gasless-store.js.map +1 -0
- package/dist/gasless-types.d.ts +218 -0
- package/dist/gasless-types.d.ts.map +1 -0
- package/dist/gasless-types.js +2 -0
- package/dist/gasless-types.js.map +1 -0
- package/dist/gasless.d.ts +135 -0
- package/dist/gasless.d.ts.map +1 -0
- package/dist/gasless.js +513 -0
- package/dist/gasless.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/limit-orders.d.ts +440 -0
- package/dist/limit-orders.d.ts.map +1 -0
- package/dist/limit-orders.js +387 -0
- package/dist/limit-orders.js.map +1 -0
- package/dist/networks/index.d.ts +3 -0
- package/dist/networks/index.d.ts.map +1 -0
- package/dist/networks/index.js +2 -0
- package/dist/networks/index.js.map +1 -0
- package/dist/networks/robinhood.d.ts +536 -0
- package/dist/networks/robinhood.d.ts.map +1 -0
- package/dist/networks/robinhood.js +489 -0
- package/dist/networks/robinhood.js.map +1 -0
- package/dist/types.d.ts +436 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/examples/gasless-nextjs-proxy.ts +87 -0
- package/examples/gasless-operator.ts +76 -0
- package/examples/gasless-swap.ts +170 -0
- package/examples/limit-order-ethers.ts +195 -0
- package/examples/nextjs-proxy.ts +82 -0
- package/examples/react-hooks.tsx +396 -0
- package/examples/swap-ethers.ts +209 -0
- package/examples/swap-web3py.py +267 -0
- package/package.json +53 -12
- package/src/constants.ts +181 -178
- package/src/gasless-client.ts +823 -0
- package/src/gasless-index.ts +4 -0
- package/src/gasless-operators.ts +227 -0
- package/src/gasless-store.ts +137 -0
- package/src/gasless-types.ts +258 -0
- package/src/gasless.ts +756 -0
- package/src/index.ts +69 -63
- package/src/limit-orders.ts +206 -195
- package/src/types.ts +80 -55
package/LIMIT-ORDERS.md
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
# Switch Limit Orders — Integration Guide
|
|
2
2
|
|
|
3
|
-
> **Gasless EIP-712 signed limit orders on PulseChain and Robinhood Chain**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
> **Gasless EIP-712 signed limit orders on PulseChain and Robinhood Chain**
|
|
4
|
+
|
|
5
|
+
This guide describes ordinary limit orders. The dedicated GaslessSwitch swap
|
|
6
|
+
option has a separate domain, contract, fee, payout cap and approval target;
|
|
7
|
+
use [GASLESS.md](GASLESS.md) and the dedicated SDK helpers for that flow.
|
|
8
|
+
|
|
9
|
+
**Limit Order API:** `https://quote.switch.win`
|
|
10
|
+
|
|
11
|
+
| API network | Chain ID | Native currency | Current limit-order contract | Native flow contract |
|
|
12
|
+
|---|---:|---|---|---|
|
|
13
|
+
| `pulsechain` | 369 | PLS | `0x2afBf0aB8d958a0227742F7a8BdA00c96372E4D7` | `0x0362177FF2ad25a33a879c881a5055575C63a4cE` |
|
|
14
|
+
| `robinhood` | 4663 | ETH | `0x1E05115387f314398bbb1A808B25308E71150396` | `0x8170a3B0e2FD2e4333E0Ca9c9414B2D3dd6aF689` |
|
|
15
|
+
|
|
16
|
+
Pass `network: "pulsechain"` or `network: "robinhood"` to every SDK API
|
|
17
|
+
helper. Call `fetchLimitOrderConfig({ network })` at startup and treat its
|
|
18
|
+
addresses and EIP-712 domain as the live source of truth.
|
|
15
19
|
|
|
16
20
|
---
|
|
17
21
|
|
|
@@ -21,7 +25,7 @@ addresses and EIP-712 domain as the live source of truth.
|
|
|
21
25
|
1. [Overview](#overview)
|
|
22
26
|
2. [How It Works](#how-it-works)
|
|
23
27
|
3. [Creating a Limit Order](#creating-a-limit-order)
|
|
24
|
-
4. [Native Currency Limit Orders (SwitchPLSFlow)](#native-currency-limit-orders-switchplsflow)
|
|
28
|
+
4. [Native Currency Limit Orders (SwitchPLSFlow)](#native-currency-limit-orders-switchplsflow)
|
|
25
29
|
5. [Choosing `feeOnOutput`](#choosing-feeonoutput)
|
|
26
30
|
6. [Querying Limit Orders](#querying-limit-orders)
|
|
27
31
|
7. [Cancelling a Limit Order](#cancelling-a-limit-order)
|
|
@@ -60,37 +64,37 @@ Switch Limit Orders let users place **gasless, signed orders** that are filled a
|
|
|
60
64
|
- **Nonce-based replay protection** — each order has a unique nonce per maker
|
|
61
65
|
- **Optional expiry** — set a deadline or make the order valid forever
|
|
62
66
|
- **Custom recipient** — output tokens can be sent to a different address
|
|
63
|
-
- **Native unwrap** — WPLS can be unwrapped to PLS and Robinhood WETH can be
|
|
64
|
-
unwrapped to ETH by setting `unwrapOutput`
|
|
65
|
-
|
|
66
|
-
> **⚠️ Important:** The `SWITCH_LIMIT_ORDER` address is the **current** default. The contract may be redeployed (e.g. when the router is upgraded). Each order returned by the API includes a `limitOrderContract` field — **always use the contract address from the order for on-chain interactions (approvals, cancellations), not a hardcoded constant.** This ensures your integration works seamlessly across contract versions without code changes.
|
|
67
|
-
|
|
68
|
-
### Network-aware setup
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import {
|
|
72
|
-
fetchLimitOrderConfig,
|
|
73
|
-
getLimitOrderApprovalTarget,
|
|
74
|
-
getNetworkEIP712SigningParams,
|
|
75
|
-
submitLimitOrder,
|
|
76
|
-
} from "@switch-win/sdk/limit-orders";
|
|
77
|
-
|
|
78
|
-
// `signer` and a completed `order` are assumed below.
|
|
79
|
-
const network = "robinhood" as const;
|
|
80
|
-
const live = await fetchLimitOrderConfig({ network });
|
|
81
|
-
const { domain, types } = getNetworkEIP712SigningParams(
|
|
82
|
-
network,
|
|
83
|
-
live.limitOrderContract,
|
|
84
|
-
);
|
|
85
|
-
const approvalTarget = getLimitOrderApprovalTarget(network, order.feeOnOutput, {
|
|
86
|
-
limitOrderContract: live.limitOrderContract,
|
|
87
|
-
});
|
|
88
|
-
const signature = await signer.signTypedData(domain, types, order);
|
|
89
|
-
await submitLimitOrder(
|
|
90
|
-
{ ...order, signature, limitOrderContract: live.limitOrderContract },
|
|
91
|
-
{ network },
|
|
92
|
-
);
|
|
93
|
-
```
|
|
67
|
+
- **Native unwrap** — WPLS can be unwrapped to PLS and Robinhood WETH can be
|
|
68
|
+
unwrapped to ETH by setting `unwrapOutput`
|
|
69
|
+
|
|
70
|
+
> **⚠️ Important:** The `SWITCH_LIMIT_ORDER` address is the **current** default. The contract may be redeployed (e.g. when the router is upgraded). Each order returned by the API includes a `limitOrderContract` field — **always use the contract address from the order for on-chain interactions (approvals, cancellations), not a hardcoded constant.** This ensures your integration works seamlessly across contract versions without code changes.
|
|
71
|
+
|
|
72
|
+
### Network-aware setup
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import {
|
|
76
|
+
fetchLimitOrderConfig,
|
|
77
|
+
getLimitOrderApprovalTarget,
|
|
78
|
+
getNetworkEIP712SigningParams,
|
|
79
|
+
submitLimitOrder,
|
|
80
|
+
} from "@switch-win/sdk/limit-orders";
|
|
81
|
+
|
|
82
|
+
// `signer` and a completed `order` are assumed below.
|
|
83
|
+
const network = "robinhood" as const;
|
|
84
|
+
const live = await fetchLimitOrderConfig({ network });
|
|
85
|
+
const { domain, types } = getNetworkEIP712SigningParams(
|
|
86
|
+
network,
|
|
87
|
+
live.limitOrderContract,
|
|
88
|
+
);
|
|
89
|
+
const approvalTarget = getLimitOrderApprovalTarget(network, order.feeOnOutput, {
|
|
90
|
+
limitOrderContract: live.limitOrderContract,
|
|
91
|
+
});
|
|
92
|
+
const signature = await signer.signTypedData(domain, types, order);
|
|
93
|
+
await submitLimitOrder(
|
|
94
|
+
{ ...order, signature, limitOrderContract: live.limitOrderContract },
|
|
95
|
+
{ network },
|
|
96
|
+
);
|
|
97
|
+
```
|
|
94
98
|
|
|
95
99
|
---
|
|
96
100
|
|
|
@@ -113,9 +117,9 @@ await submitLimitOrder(
|
|
|
113
117
|
│ Tokens are pulled from the maker's wallet and swapped. │
|
|
114
118
|
│ Output is sent to the maker (or custom recipient). │
|
|
115
119
|
│ │
|
|
116
|
-
│ 5. CANCEL (optional) │
|
|
117
|
-
│ invalidateNonce(nonce) on-chain — prevents execution │
|
|
118
|
-
│ The backend indexer observes the cancellation event. │
|
|
120
|
+
│ 5. CANCEL (optional) │
|
|
121
|
+
│ invalidateNonce(nonce) on-chain — prevents execution │
|
|
122
|
+
│ The backend indexer observes the cancellation event. │
|
|
119
123
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
120
124
|
```
|
|
121
125
|
|
|
@@ -131,10 +135,12 @@ await submitLimitOrder(
|
|
|
131
135
|
> Your integration must:
|
|
132
136
|
> 1. Call `submitLimitOrder()` **immediately** after the user signs.
|
|
133
137
|
> 2. **Await** the response and confirm `success: true` before showing "order created".
|
|
134
|
-
> 3.
|
|
138
|
+
> 3. On a transient failure, look up that exact maker + nonce and verify its
|
|
139
|
+
> contract and signed fields. Duplicates return an error, not idempotent
|
|
140
|
+
> success. Retry only the original signed payload; never generate a new nonce.
|
|
135
141
|
> 4. Do **not** navigate away or close the signing flow until the backend confirms.
|
|
136
142
|
>
|
|
137
|
-
> *Native PLS/ETH flow orders are the exception — they are recorded on-chain*
|
|
143
|
+
> *Native PLS/ETH flow orders are the exception — they are recorded on-chain*
|
|
138
144
|
> *first, so the backend discovers them via event indexing even if the POST*
|
|
139
145
|
> *never arrives.*
|
|
140
146
|
|
|
@@ -146,28 +152,28 @@ The full lifecycle in code (ethers.js v6):
|
|
|
146
152
|
|
|
147
153
|
```ts
|
|
148
154
|
import { ethers } from "ethers";
|
|
149
|
-
import {
|
|
150
|
-
buildLimitOrder,
|
|
151
|
-
fetchLimitOrderConfig,
|
|
152
|
-
getLimitOrderApprovalTarget,
|
|
153
|
-
getNetworkEIP712SigningParams,
|
|
154
|
-
submitLimitOrder,
|
|
155
|
-
} from "@switch-win/sdk/limit-orders";
|
|
155
|
+
import {
|
|
156
|
+
buildLimitOrder,
|
|
157
|
+
fetchLimitOrderConfig,
|
|
158
|
+
getLimitOrderApprovalTarget,
|
|
159
|
+
getNetworkEIP712SigningParams,
|
|
160
|
+
submitLimitOrder,
|
|
161
|
+
} from "@switch-win/sdk/limit-orders";
|
|
156
162
|
import { ERC20_ABI } from "@switch-win/sdk/constants";
|
|
157
163
|
|
|
158
|
-
const network = "robinhood" as const;
|
|
159
|
-
const provider = new ethers.JsonRpcProvider("https://rpc.mainnet.chain.robinhood.com");
|
|
160
|
-
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
161
|
-
const maker = await signer.getAddress();
|
|
162
|
-
const live = await fetchLimitOrderConfig({ network });
|
|
164
|
+
const network = "robinhood" as const;
|
|
165
|
+
const provider = new ethers.JsonRpcProvider("https://rpc.mainnet.chain.robinhood.com");
|
|
166
|
+
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
167
|
+
const maker = await signer.getAddress();
|
|
168
|
+
const live = await fetchLimitOrderConfig({ network });
|
|
163
169
|
|
|
164
170
|
// ── Step 1: Build the order ──
|
|
165
171
|
const order = buildLimitOrder({
|
|
166
172
|
maker,
|
|
167
|
-
tokenIn: "0x0Bd7D308f8E1639FAb988Df18A8011f41EAcAD73", // WETH
|
|
168
|
-
tokenOut: "0x5fc5360D0400a0Fd4F2aF552aDD042D716F1d168", // USDG
|
|
169
|
-
amountIn: ethers.parseUnits("0.01", 18).toString(),
|
|
170
|
-
minAmountOut: ethers.parseUnits("30", 18).toString(),
|
|
173
|
+
tokenIn: "0x0Bd7D308f8E1639FAb988Df18A8011f41EAcAD73", // WETH
|
|
174
|
+
tokenOut: "0x5fc5360D0400a0Fd4F2aF552aDD042D716F1d168", // USDG
|
|
175
|
+
amountIn: ethers.parseUnits("0.01", 18).toString(),
|
|
176
|
+
minAmountOut: ethers.parseUnits("30", 18).toString(),
|
|
171
177
|
deadline: Math.floor(Date.now() / 1000) + 86400, // 24h expiry
|
|
172
178
|
// nonce: auto-generated from Date.now()
|
|
173
179
|
// feeOnOutput: false (default — fee taken from input)
|
|
@@ -176,11 +182,11 @@ const order = buildLimitOrder({
|
|
|
176
182
|
});
|
|
177
183
|
|
|
178
184
|
// ── Step 2: Approve the correct contract (one-time per token + fee mode) ──
|
|
179
|
-
// feeOnOutput=false (default) → approve this network's LO contract
|
|
180
|
-
// feeOnOutput=true → approve this network's SwitchRouter
|
|
181
|
-
const approvalTarget = getLimitOrderApprovalTarget(network, order.feeOnOutput, {
|
|
182
|
-
limitOrderContract: live.limitOrderContract,
|
|
183
|
-
});
|
|
185
|
+
// feeOnOutput=false (default) → approve this network's LO contract
|
|
186
|
+
// feeOnOutput=true → approve this network's SwitchRouter
|
|
187
|
+
const approvalTarget = getLimitOrderApprovalTarget(network, order.feeOnOutput, {
|
|
188
|
+
limitOrderContract: live.limitOrderContract,
|
|
189
|
+
});
|
|
184
190
|
const token = new ethers.Contract(order.tokenIn, ERC20_ABI, signer);
|
|
185
191
|
const allowance: bigint = await token.allowance(maker, approvalTarget);
|
|
186
192
|
|
|
@@ -190,23 +196,23 @@ if (allowance < BigInt(order.amountIn)) {
|
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
// ── Step 3: Sign via EIP-712 (gasless!) ──
|
|
193
|
-
const { domain, types } = getNetworkEIP712SigningParams(
|
|
194
|
-
network,
|
|
195
|
-
live.limitOrderContract,
|
|
196
|
-
);
|
|
197
|
-
const signature = await signer.signTypedData(domain, types, order);
|
|
199
|
+
const { domain, types } = getNetworkEIP712SigningParams(
|
|
200
|
+
network,
|
|
201
|
+
live.limitOrderContract,
|
|
202
|
+
);
|
|
203
|
+
const signature = await signer.signTypedData(domain, types, order);
|
|
198
204
|
|
|
199
205
|
// ── Step 4: Submit to the Switch backend ──
|
|
200
206
|
// ⚠️ CRITICAL: The signature is off-chain only — if this POST fails,
|
|
201
207
|
// the order is lost. Submit IMMEDIATELY after signing, await the
|
|
202
208
|
// response, and retry on network failure.
|
|
203
|
-
const result = await submitLimitOrder(
|
|
204
|
-
{ ...order, signature, limitOrderContract: live.limitOrderContract },
|
|
205
|
-
{ network },
|
|
206
|
-
);
|
|
209
|
+
const result = await submitLimitOrder(
|
|
210
|
+
{ ...order, signature, limitOrderContract: live.limitOrderContract },
|
|
211
|
+
{ network },
|
|
212
|
+
);
|
|
207
213
|
|
|
208
214
|
if ("error" in result) {
|
|
209
|
-
//
|
|
215
|
+
// Look up the exact order before retrying the identical signed payload.
|
|
210
216
|
console.error("Failed:", result.error);
|
|
211
217
|
} else {
|
|
212
218
|
console.log("Order created:", result.order.id);
|
|
@@ -227,62 +233,68 @@ if ("error" in result) {
|
|
|
227
233
|
| `nonce` | `number` | No | `Date.now()` | Unique per maker. Auto-generated if omitted |
|
|
228
234
|
| `feeOnOutput` | `boolean` | No | `false` | Fee mode — see [Choosing `feeOnOutput`](#choosing-feeonoutput) below |
|
|
229
235
|
| `recipient` | `string` | No | `maker` | Address to receive output tokens |
|
|
230
|
-
| `unwrapOutput` | `boolean` | No | `false` | Unwrap wrapped native output to PLS/ETH on the selected network |
|
|
236
|
+
| `unwrapOutput` | `boolean` | No | `false` | Unwrap wrapped native output to PLS/ETH on the selected network |
|
|
231
237
|
|
|
232
238
|
---
|
|
233
239
|
|
|
234
|
-
## Native Currency Limit Orders (SwitchPLSFlow)
|
|
235
|
-
|
|
236
|
-
When selling native **PLS** on PulseChain or native **ETH** on Robinhood
|
|
237
|
-
(rather than WPLS/WETH), use the selected network's native-flow contract
|
|
238
|
-
instead of the standard EIP-712 signing flow. The deployed contract retains
|
|
239
|
-
the legacy `SwitchPLSFlow` name on both networks, but its behavior is
|
|
240
|
-
native-currency neutral:
|
|
240
|
+
## Native Currency Limit Orders (SwitchPLSFlow)
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
When selling native **PLS** on PulseChain or native **ETH** on Robinhood
|
|
243
|
+
(rather than WPLS/WETH), use the selected network's native-flow contract
|
|
244
|
+
instead of the standard EIP-712 signing flow. The deployed contract retains
|
|
245
|
+
the legacy `SwitchPLSFlow` name on both networks, but its behavior is
|
|
246
|
+
native-currency neutral:
|
|
247
|
+
|
|
248
|
+
- **No approval needed** — users send the native currency directly to the contract
|
|
243
249
|
- **No EIP-712 signature** — the contract creates the order on-chain immediately
|
|
244
250
|
- **Single transaction** — wrap + approve + place order all in one tx
|
|
245
|
-
- **Fully indexed** — the backend discovers orders via `PLSOrderCreated` events
|
|
246
|
-
- **Drain-mode capable** — maintainers can disable new native deposits without
|
|
247
|
-
blocking fills or cancellations of existing orders
|
|
251
|
+
- **Fully indexed** — the backend discovers orders via `PLSOrderCreated` events
|
|
252
|
+
- **Drain-mode capable** — maintainers can disable new native deposits without
|
|
253
|
+
blocking fills or cancellations of existing orders
|
|
254
|
+
|
|
255
|
+
> **Current ABI:** native-flow deployments use the seven-argument
|
|
256
|
+
> `createOrder(tokenOut, minAmountOut, deadline, feeOnOutput, unwrapOutput,
|
|
257
|
+
> partnerAddress, recipient)` signature. The `recipient` argument must be
|
|
258
|
+
> present in calldata; pass the zero address to default it to `msg.sender`.
|
|
259
|
+
> The older six-argument selector is not compatible with current deployments.
|
|
248
260
|
|
|
249
|
-
### Native-flow contract address
|
|
261
|
+
### Native-flow contract address
|
|
250
262
|
|
|
251
263
|
```ts
|
|
252
|
-
import { getNativeFlowAddress, PLS_FLOW_ABI } from "@switch-win/sdk";
|
|
253
|
-
|
|
254
|
-
const robinhoodNativeFlow = getNativeFlowAddress("robinhood");
|
|
255
|
-
// "0x8170a3B0e2FD2e4333E0Ca9c9414B2D3dd6aF689"
|
|
264
|
+
import { getNativeFlowAddress, PLS_FLOW_ABI } from "@switch-win/sdk";
|
|
265
|
+
|
|
266
|
+
const robinhoodNativeFlow = getNativeFlowAddress("robinhood");
|
|
267
|
+
// "0x8170a3B0e2FD2e4333E0Ca9c9414B2D3dd6aF689"
|
|
256
268
|
```
|
|
257
269
|
|
|
258
|
-
### Creating a native ETH limit order on Robinhood
|
|
270
|
+
### Creating a native ETH limit order on Robinhood
|
|
259
271
|
|
|
260
272
|
```ts
|
|
261
273
|
import { ethers } from "ethers";
|
|
262
|
-
import { getNativeFlowAddress, isNativeCurrency } from "@switch-win/sdk/limit-orders";
|
|
263
|
-
import { PLS_FLOW_ABI } from "@switch-win/sdk/constants";
|
|
264
|
-
|
|
265
|
-
const network = "robinhood" as const;
|
|
266
|
-
const provider = new ethers.JsonRpcProvider("https://rpc.mainnet.chain.robinhood.com");
|
|
267
|
-
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
268
|
-
|
|
269
|
-
const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
270
|
-
if (isNativeCurrency(NATIVE)) {
|
|
271
|
-
const nativeFlow = new ethers.Contract(
|
|
272
|
-
getNativeFlowAddress(network),
|
|
273
|
-
PLS_FLOW_ABI,
|
|
274
|
-
signer,
|
|
275
|
-
);
|
|
274
|
+
import { getNativeFlowAddress, isNativeCurrency } from "@switch-win/sdk/limit-orders";
|
|
275
|
+
import { PLS_FLOW_ABI } from "@switch-win/sdk/constants";
|
|
276
|
+
|
|
277
|
+
const network = "robinhood" as const;
|
|
278
|
+
const provider = new ethers.JsonRpcProvider("https://rpc.mainnet.chain.robinhood.com");
|
|
279
|
+
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
280
|
+
|
|
281
|
+
const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
282
|
+
if (isNativeCurrency(NATIVE)) {
|
|
283
|
+
const nativeFlow = new ethers.Contract(
|
|
284
|
+
getNativeFlowAddress(network),
|
|
285
|
+
PLS_FLOW_ABI,
|
|
286
|
+
signer,
|
|
287
|
+
);
|
|
276
288
|
|
|
277
|
-
const tokenOut = "0x5fc5360D0400a0Fd4F2aF552aDD042D716F1d168"; // USDG
|
|
278
|
-
const amountIn = ethers.parseEther("0.01");
|
|
279
|
-
const minAmountOut = ethers.parseUnits("30", 18);
|
|
289
|
+
const tokenOut = "0x5fc5360D0400a0Fd4F2aF552aDD042D716F1d168"; // USDG
|
|
290
|
+
const amountIn = ethers.parseEther("0.01");
|
|
291
|
+
const minAmountOut = ethers.parseUnits("30", 18);
|
|
280
292
|
const deadline = Math.floor(Date.now() / 1000) + 86400; // 24h expiry
|
|
281
293
|
const feeOnOutput = false;
|
|
282
294
|
const unwrapOutput = false;
|
|
283
295
|
|
|
284
296
|
// Single transaction — no approval, no signing
|
|
285
|
-
const tx = await nativeFlow.createOrder(
|
|
297
|
+
const tx = await nativeFlow.createOrder(
|
|
286
298
|
tokenOut,
|
|
287
299
|
minAmountOut,
|
|
288
300
|
deadline,
|
|
@@ -294,65 +306,65 @@ if (isNativeCurrency(NATIVE)) {
|
|
|
294
306
|
);
|
|
295
307
|
|
|
296
308
|
const receipt = await tx.wait();
|
|
297
|
-
console.log("Native ETH limit order created in tx:", receipt.hash);
|
|
309
|
+
console.log("Native ETH limit order created in tx:", receipt.hash);
|
|
298
310
|
|
|
299
311
|
// The backend will automatically detect this order via PLSOrderCreated event.
|
|
300
312
|
// No POST to /limit-orders required (though it's harmless if you do).
|
|
301
313
|
}
|
|
302
314
|
```
|
|
303
315
|
|
|
304
|
-
### Native flow vs EIP-712 orders
|
|
316
|
+
### Native flow vs EIP-712 orders
|
|
305
317
|
|
|
306
|
-
| Aspect | Native flow (PLS/ETH) | EIP-712 (ERC-20) |
|
|
318
|
+
| Aspect | Native flow (PLS/ETH) | EIP-712 (ERC-20) |
|
|
307
319
|
|---|---|---|
|
|
308
|
-
| **Input token** | Native currency | Any ERC-20 (including WPLS/WETH) |
|
|
320
|
+
| **Input token** | Native currency | Any ERC-20 (including WPLS/WETH) |
|
|
309
321
|
| **User experience** | Single transaction | Approve + Sign + Submit |
|
|
310
322
|
| **Gas cost** | User pays gas | Gasless signing (user pays on fill) |
|
|
311
323
|
| **Order discovery** | On-chain event | Requires successful POST |
|
|
312
|
-
| **Maker address** | Native-flow contract | User's wallet |
|
|
324
|
+
| **Maker address** | Native-flow contract | User's wallet |
|
|
313
325
|
| **Recipient** | User's wallet (or custom) | User's wallet (or custom) |
|
|
314
326
|
|
|
315
327
|
### Important: Order Discovery
|
|
316
328
|
|
|
317
|
-
For native-flow orders, the `maker` field in the order record is the
|
|
318
|
-
**native-flow contract address**, not the user's address. The actual user is
|
|
319
|
-
stored in the `recipient` field.
|
|
329
|
+
For native-flow orders, the `maker` field in the order record is the
|
|
330
|
+
**native-flow contract address**, not the user's address. The actual user is
|
|
331
|
+
stored in the `recipient` field.
|
|
320
332
|
|
|
321
333
|
When querying orders for a user, use the `owner` parameter instead of `maker`:
|
|
322
334
|
|
|
323
335
|
```ts
|
|
324
|
-
// ✅ Correct — finds both EIP-712 and native-flow orders
|
|
325
|
-
const { orders } = await fetchLimitOrders({
|
|
326
|
-
network: "robinhood",
|
|
336
|
+
// ✅ Correct — finds both EIP-712 and native-flow orders
|
|
337
|
+
const { orders } = await fetchLimitOrders({
|
|
338
|
+
network: "robinhood",
|
|
327
339
|
owner: "0xUserAddress", // matches maker OR recipient
|
|
328
340
|
status: "ACTIVE",
|
|
329
341
|
});
|
|
330
342
|
|
|
331
|
-
// ❌ Won't find native-flow orders
|
|
332
|
-
const { orders } = await fetchLimitOrders({
|
|
333
|
-
network: "robinhood",
|
|
343
|
+
// ❌ Won't find native-flow orders
|
|
344
|
+
const { orders } = await fetchLimitOrders({
|
|
345
|
+
network: "robinhood",
|
|
334
346
|
maker: "0xUserAddress", // only matches maker field
|
|
335
347
|
status: "ACTIVE",
|
|
336
348
|
});
|
|
337
349
|
```
|
|
338
350
|
|
|
339
|
-
### Cancelling a native-flow order
|
|
351
|
+
### Cancelling a native-flow order
|
|
340
352
|
|
|
341
|
-
Native-flow orders can be cancelled by the original creator (`recipient`):
|
|
353
|
+
Native-flow orders can be cancelled by the original creator (`recipient`):
|
|
342
354
|
|
|
343
355
|
```ts
|
|
344
|
-
const nativeFlow = new ethers.Contract(
|
|
345
|
-
getNativeFlowAddress("robinhood"),
|
|
346
|
-
PLS_FLOW_ABI,
|
|
347
|
-
signer,
|
|
348
|
-
);
|
|
356
|
+
const nativeFlow = new ethers.Contract(
|
|
357
|
+
getNativeFlowAddress("robinhood"),
|
|
358
|
+
PLS_FLOW_ABI,
|
|
359
|
+
signer,
|
|
360
|
+
);
|
|
349
361
|
|
|
350
362
|
// Cancel on-chain (only the original creator can call this)
|
|
351
|
-
const tx = await nativeFlow.cancelOrder(nonceToCancel);
|
|
363
|
+
const tx = await nativeFlow.cancelOrder(nonceToCancel);
|
|
352
364
|
await tx.wait();
|
|
353
365
|
|
|
354
366
|
// The backend will detect the NonceCancelled event and update the order status.
|
|
355
|
-
// No REST mutation is required.
|
|
367
|
+
// No REST mutation is required.
|
|
356
368
|
```
|
|
357
369
|
|
|
358
370
|
---
|
|
@@ -383,23 +395,23 @@ When `feeOnOutput=false`, the LO contract pulls all tokens to itself first — o
|
|
|
383
395
|
|
|
384
396
|
When `feeOnOutput=true`, the Router pulls tokens directly from the maker. If an operator also tries to take excess input via the LO contract, that requires a second approval the maker typically hasn't granted — the transaction would simply revert. The operator loses gas; the maker loses nothing and the order remains active for another operator. In practice, operators use output-side profit for `feeOnOutput=true` orders.
|
|
385
397
|
|
|
386
|
-
> **Impact on fillability:** `feeOnOutput=true` orders are still fully fillable, but operators have less flexibility. In tight-margin or low-liquidity situations, this *could* reduce the likelihood of a fill since operators cannot optimize their profit-taking strategy on both sides.
|
|
387
|
-
|
|
388
|
-
### Protected settlement
|
|
389
|
-
|
|
390
|
-
The current PulseChain and Robinhood deployments charge a 30 bps (0.30%)
|
|
391
|
-
limit-order fee. They are exempt from the Router's separate 10 bps regular-swap
|
|
392
|
-
fee, so the two fees are not stacked.
|
|
393
|
-
|
|
394
|
-
For routed fills, operator-retained surplus is capped at 5%. Input-side profit
|
|
395
|
-
is capped at 5% of executable input; output-side profit is capped at 5% of the
|
|
396
|
-
signed `minAmountOut`. Any additional routing improvement is returned to the
|
|
397
|
-
maker.
|
|
398
|
-
|
|
399
|
-
Direct fills are protected by the configured `directFillQuoter`. The maker must
|
|
400
|
-
receive at least the greater of the signed `minAmountOut` and 95% of the best
|
|
401
|
-
current direct-adapter quote. A missing or zero protected quote fails closed,
|
|
402
|
-
and any post-fee output sent above the protected floor belongs to the maker.
|
|
398
|
+
> **Impact on fillability:** `feeOnOutput=true` orders are still fully fillable, but operators have less flexibility. In tight-margin or low-liquidity situations, this *could* reduce the likelihood of a fill since operators cannot optimize their profit-taking strategy on both sides.
|
|
399
|
+
|
|
400
|
+
### Protected settlement
|
|
401
|
+
|
|
402
|
+
The current PulseChain and Robinhood deployments charge a 30 bps (0.30%)
|
|
403
|
+
limit-order fee. They are exempt from the Router's separate 10 bps regular-swap
|
|
404
|
+
fee, so the two fees are not stacked.
|
|
405
|
+
|
|
406
|
+
For routed fills, operator-retained surplus is capped at 5%. Input-side profit
|
|
407
|
+
is capped at 5% of executable input; output-side profit is capped at 5% of the
|
|
408
|
+
signed `minAmountOut`. Any additional routing improvement is returned to the
|
|
409
|
+
maker.
|
|
410
|
+
|
|
411
|
+
Direct fills are protected by the configured `directFillQuoter`. The maker must
|
|
412
|
+
receive at least the greater of the signed `minAmountOut` and 95% of the best
|
|
413
|
+
current direct-adapter quote. A missing or zero protected quote fails closed,
|
|
414
|
+
and any post-fee output sent above the protected floor belongs to the maker.
|
|
403
415
|
|
|
404
416
|
### Tax token decision guide
|
|
405
417
|
|
|
@@ -408,7 +420,7 @@ and any post-fee output sent above the protected floor belongs to the maker.
|
|
|
408
420
|
| **Neither token is taxed** | `false` (default) | Maximum operator flexibility → best chance of fill |
|
|
409
421
|
| **Input token is taxed** | `true` | Router sends tokens directly from maker → pool in one transfer (one tax). Default mode would do maker → LO → pool (two transfers, two taxes). |
|
|
410
422
|
| **Output token is taxed** | `false` (default) | Output goes directly to recipient in one transfer. |
|
|
411
|
-
| **Both tokens taxed** | `false` | Supported through tax-safe routes; fee on input avoids another output-token transfer. |
|
|
423
|
+
| **Both tokens taxed** | `false` | Supported through tax-safe routes; fee on input avoids another output-token transfer. |
|
|
412
424
|
|
|
413
425
|
> **Recommendation:** Use `feeOnOutput: false` (the default) unless the input token has a transfer tax. This gives operators the most flexibility and the best chance of filling your order.
|
|
414
426
|
|
|
@@ -468,18 +480,18 @@ const stats = await fetchLimitOrderStats();
|
|
|
468
480
|
|
|
469
481
|
## Cancelling a Limit Order
|
|
470
482
|
|
|
471
|
-
Cancellation is an **on-chain operation**:
|
|
483
|
+
Cancellation is an **on-chain operation**:
|
|
472
484
|
|
|
473
485
|
1. **On-chain:** Call `invalidateNonce(nonce)` on the **order's** SwitchLimitOrder contract. This is the authoritative cancellation — it prevents any operator from executing the order even if the backend hasn't been notified yet.
|
|
474
486
|
|
|
475
487
|
> **⚠️** Each order includes a `limitOrderContract` field. Always use that address — do not hardcode a single contract constant, as the contract may be redeployed across versions.
|
|
476
488
|
|
|
477
|
-
The backend indexer observes `NonceCancelled` and updates the orderbook. No
|
|
478
|
-
separate REST mutation is required.
|
|
489
|
+
The backend indexer observes `NonceCancelled` and updates the orderbook. No
|
|
490
|
+
separate REST mutation is required.
|
|
479
491
|
|
|
480
492
|
```ts
|
|
481
493
|
import { ethers } from "ethers";
|
|
482
|
-
import { LIMIT_ORDER_ABI } from "@switch-win/sdk/constants";
|
|
494
|
+
import { LIMIT_ORDER_ABI } from "@switch-win/sdk/constants";
|
|
483
495
|
|
|
484
496
|
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
485
497
|
const maker = await signer.getAddress();
|
|
@@ -491,7 +503,7 @@ const contract = new ethers.Contract(order.limitOrderContract, LIMIT_ORDER_ABI,
|
|
|
491
503
|
const tx = await contract.invalidateNonce(nonceToCancel);
|
|
492
504
|
await tx.wait();
|
|
493
505
|
|
|
494
|
-
// The backend indexer observes the event and marks the order CANCELLED.
|
|
506
|
+
// The backend indexer observes the event and marks the order CANCELLED.
|
|
495
507
|
```
|
|
496
508
|
|
|
497
509
|
To cancel **multiple orders** at once, use `invalidateNonces(uint256[])`:
|
|
@@ -501,7 +513,7 @@ const nonces = [1717171717, 1717171718, 1717171719];
|
|
|
501
513
|
const tx = await contract.invalidateNonces(nonces);
|
|
502
514
|
await tx.wait();
|
|
503
515
|
|
|
504
|
-
// The backend indexer observes each NonceCancelled event.
|
|
516
|
+
// The backend indexer observes each NonceCancelled event.
|
|
505
517
|
```
|
|
506
518
|
|
|
507
519
|
---
|
|
@@ -530,9 +542,11 @@ Submit a signed limit order. The backend verifies the EIP-712 signature before s
|
|
|
530
542
|
> Your integration must:
|
|
531
543
|
> 1. Call `submitLimitOrder()` **immediately** after the user signs.
|
|
532
544
|
> 2. **Await** the response and confirm `success: true` before showing "order created".
|
|
533
|
-
> 3.
|
|
545
|
+
> 3. On a transient failure, look up that exact maker + nonce and verify its
|
|
546
|
+
> contract and signed fields. Duplicates return an error, not idempotent
|
|
547
|
+
> success. Retry only the original signed payload; never generate a new nonce.
|
|
534
548
|
>
|
|
535
|
-
> Native PLS/ETH flow orders are the exception — they are recorded on-chain first,
|
|
549
|
+
> Native PLS/ETH flow orders are the exception — they are recorded on-chain first,
|
|
536
550
|
> so the backend discovers them via event indexing even if the POST never arrives.
|
|
537
551
|
|
|
538
552
|
**Request body:**
|
|
@@ -547,11 +561,11 @@ Submit a signed limit order. The backend verifies the EIP-712 signature before s
|
|
|
547
561
|
"deadline": 1717257600,
|
|
548
562
|
"nonce": 1717171717,
|
|
549
563
|
"feeOnOutput": false,
|
|
550
|
-
"recipient": "0x...",
|
|
551
|
-
"unwrapOutput": false,
|
|
552
|
-
"limitOrderContract": "0x...",
|
|
553
|
-
"signature": "0x..."
|
|
554
|
-
}
|
|
564
|
+
"recipient": "0x...",
|
|
565
|
+
"unwrapOutput": false,
|
|
566
|
+
"limitOrderContract": "0x...",
|
|
567
|
+
"signature": "0x..."
|
|
568
|
+
}
|
|
555
569
|
```
|
|
556
570
|
|
|
557
571
|
**Success response (201):**
|
|
@@ -593,7 +607,7 @@ Submit a signed limit order. The backend verifies the EIP-712 signature before s
|
|
|
593
607
|
| `"Signature does not match maker"` | The recovered signer doesn't match the `maker` field |
|
|
594
608
|
| `"Nonce N already used for maker 0x..."` | This nonce has already been used (filled or submitted) |
|
|
595
609
|
|
|
596
|
-
### `GET /limit-orders`
|
|
610
|
+
### `GET /limit-orders`
|
|
597
611
|
|
|
598
612
|
List orders with optional query filters. See [Query Filters](#query-filters) above.
|
|
599
613
|
|
|
@@ -663,13 +677,13 @@ Summary statistics.
|
|
|
663
677
|
| `LimitOrderParams` | Order fields for EIP-712 signing |
|
|
664
678
|
| `SignedLimitOrder` | `LimitOrderParams` + `signature` |
|
|
665
679
|
| `CreateLimitOrderRequest` | Alias for `SignedLimitOrder` (POST body) |
|
|
666
|
-
| `LimitOrderStatus` | `"ACTIVE" \| "FILLED" \| "CANCELLED" \| "EXPIRED"` |
|
|
680
|
+
| `LimitOrderStatus` | `"ACTIVE" \| "FILLED" \| "CANCELLED" \| "EXPIRED"` |
|
|
667
681
|
| `LimitOrderRecord` | Full order record from the API (includes `partnerAddress`, `limitOrderContract`, status, and timestamps) |
|
|
668
682
|
| `CreateLimitOrderResponse` | `{ success: true, order: LimitOrderRecord }` |
|
|
669
683
|
| `ListLimitOrdersResponse` | `{ total, limit, offset, orders: LimitOrderRecord[] }` |
|
|
670
684
|
| `LimitOrderPair` | Active pair with order count |
|
|
671
685
|
| `LimitOrderStats` | Global order statistics |
|
|
672
|
-
| `LimitOrderMutationResponse` | Submission response + `ErrorResponse` |
|
|
686
|
+
| `LimitOrderMutationResponse` | Submission response + `ErrorResponse` |
|
|
673
687
|
|
|
674
688
|
---
|
|
675
689
|
|
|
@@ -680,23 +694,23 @@ Available from `@switch-win/sdk/limit-orders`:
|
|
|
680
694
|
| Function | Description |
|
|
681
695
|
|---|---|
|
|
682
696
|
| `buildLimitOrder(options)` | Build a `LimitOrderParams` object with sensible defaults |
|
|
683
|
-
| `getEIP712SigningParams()` | Get the `{ domain, types }` for `signTypedData()` |
|
|
684
|
-
| `getNetworkEIP712SigningParams(network, contract?)` | Build the correct PulseChain or Robinhood EIP-712 domain |
|
|
685
|
-
| `getLimitOrderNetworkConfig(network)` | Get static chain, router, LO, wrapped-native, and native-flow defaults |
|
|
686
|
-
| `getLimitOrderApprovalTarget(network, feeOnOutput, overrides?)` | Resolve the correct maker approval target |
|
|
697
|
+
| `getEIP712SigningParams()` | Get the `{ domain, types }` for `signTypedData()` |
|
|
698
|
+
| `getNetworkEIP712SigningParams(network, contract?)` | Build the correct PulseChain or Robinhood EIP-712 domain |
|
|
699
|
+
| `getLimitOrderNetworkConfig(network)` | Get static chain, router, LO, wrapped-native, and native-flow defaults |
|
|
700
|
+
| `getLimitOrderApprovalTarget(network, feeOnOutput, overrides?)` | Resolve the correct maker approval target |
|
|
687
701
|
| `getApprovalTarget()` | Get approval target for `feeOnOutput: false` orders → SwitchLimitOrder |
|
|
688
702
|
| `getRouterApprovalTarget()` | Get approval target for `feeOnOutput: true` orders → SwitchRouter |
|
|
689
703
|
| `shouldUnwrapOutput(tokenOut)` | Returns `true` if tokenOut is WPLS (should set `unwrapOutput: true`) |
|
|
690
|
-
| `getPLSFlowAddress()` | Get the SwitchPLSFlow contract address for native PLS limit orders |
|
|
691
|
-
| `getNativeFlowAddress(network)` | Get the native PLS/ETH flow address for either chain |
|
|
692
|
-
| `isNativePLS(tokenIn)` | Returns `true` if tokenIn is native PLS (use PLSFlow instead of EIP-712) |
|
|
693
|
-
| `isNativeCurrency(tokenIn)` | Network-neutral native sentinel check |
|
|
704
|
+
| `getPLSFlowAddress()` | Get the SwitchPLSFlow contract address for native PLS limit orders |
|
|
705
|
+
| `getNativeFlowAddress(network)` | Get the native PLS/ETH flow address for either chain |
|
|
706
|
+
| `isNativePLS(tokenIn)` | Returns `true` if tokenIn is native PLS (use PLSFlow instead of EIP-712) |
|
|
707
|
+
| `isNativeCurrency(tokenIn)` | Network-neutral native sentinel check |
|
|
694
708
|
| `submitLimitOrder(signedOrder)` | POST a signed order to the Switch backend |
|
|
695
709
|
| `fetchLimitOrders(options?)` | GET orders with optional filters |
|
|
696
710
|
| `fetchLimitOrder(maker, nonce)` | GET a single order by maker + nonce |
|
|
697
|
-
| `fetchLimitOrderPairs()` | GET active pairs with order counts |
|
|
698
|
-
| `fetchLimitOrderConfig({ network })` | GET live deployments and EIP-712 domain |
|
|
699
|
-
| `fetchLimitOrderStats()` | GET global order statistics |
|
|
711
|
+
| `fetchLimitOrderPairs()` | GET active pairs with order counts |
|
|
712
|
+
| `fetchLimitOrderConfig({ network })` | GET live deployments and EIP-712 domain |
|
|
713
|
+
| `fetchLimitOrderStats()` | GET global order statistics |
|
|
700
714
|
|
|
701
715
|
---
|
|
702
716
|
|
|
@@ -723,12 +737,12 @@ The EIP-712 domain and types must match the on-chain contract exactly:
|
|
|
723
737
|
{ name: "minAmountOut", type: "uint256" },
|
|
724
738
|
{ name: "deadline", type: "uint256" },
|
|
725
739
|
{ name: "nonce", type: "uint256" },
|
|
726
|
-
{ name: "feeOnOutput", type: "bool" },
|
|
727
|
-
{ name: "recipient", type: "address" },
|
|
728
|
-
{ name: "unwrapOutput", type: "bool" },
|
|
729
|
-
{ name: "partnerAddress", type: "address" },
|
|
730
|
-
]
|
|
731
|
-
}
|
|
740
|
+
{ name: "feeOnOutput", type: "bool" },
|
|
741
|
+
{ name: "recipient", type: "address" },
|
|
742
|
+
{ name: "unwrapOutput", type: "bool" },
|
|
743
|
+
{ name: "partnerAddress", type: "address" },
|
|
744
|
+
]
|
|
745
|
+
}
|
|
732
746
|
```
|
|
733
747
|
|
|
734
748
|
These are exported as `LIMIT_ORDER_EIP712_DOMAIN` and `LIMIT_ORDER_EIP712_TYPES` from `@switch-win/sdk/constants`.
|
|
@@ -746,16 +760,16 @@ These are exported as `LIMIT_ORDER_EIP712_DOMAIN` and `LIMIT_ORDER_EIP712_TYPES`
|
|
|
746
760
|
| `InvalidTokens()` | tokenIn and tokenOut are the same, or zero address |
|
|
747
761
|
| `NonceAlreadyUsed()` | This nonce has already been filled or invalidated |
|
|
748
762
|
| `OrderExpired()` | `block.timestamp > deadline` (and deadline > 0) |
|
|
749
|
-
| `RouteInputExceedsMax()` | Route's total input exceeds the order's `amountIn` |
|
|
750
|
-
| `NativeFlowInputNotFullyConsumed()` | Native-flow output-surplus route did not consume all executable input |
|
|
751
|
-
| `RouteTokenInMismatch()` | A route does not start with the signed input token |
|
|
752
|
-
| `RouteTokenOutMismatch()` | A route does not end with the signed output token |
|
|
753
|
-
| `DirectFillQuoteUnavailable()` | The protected direct-fill quote is missing or zero |
|
|
754
|
-
| `DirectFillPriceTooLow()` | The maker would receive less than the protected direct-fill floor |
|
|
755
|
-
| `TransferFailed()` | Token transfer failed (insufficient balance or allowance) |
|
|
763
|
+
| `RouteInputExceedsMax()` | Route's total input exceeds the order's `amountIn` |
|
|
764
|
+
| `NativeFlowInputNotFullyConsumed()` | Native-flow output-surplus route did not consume all executable input |
|
|
765
|
+
| `RouteTokenInMismatch()` | A route does not start with the signed input token |
|
|
766
|
+
| `RouteTokenOutMismatch()` | A route does not end with the signed output token |
|
|
767
|
+
| `DirectFillQuoteUnavailable()` | The protected direct-fill quote is missing or zero |
|
|
768
|
+
| `DirectFillPriceTooLow()` | The maker would receive less than the protected direct-fill floor |
|
|
769
|
+
| `TransferFailed()` | Token transfer failed (insufficient balance or allowance) |
|
|
756
770
|
|
|
757
771
|
---
|
|
758
772
|
|
|
759
773
|
*See also: [README.md](README.md) for swap API docs, constants, and general integration info.*
|
|
760
774
|
|
|
761
|
-
*Last updated: August 2026*
|
|
775
|
+
*Last updated: August 2026*
|