@switch-win/sdk 1.2.9 → 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.
Files changed (73) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/GASLESS.md +407 -0
  3. package/LIMIT-ORDERS.md +213 -205
  4. package/README.md +131 -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 +81 -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 +218 -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 +170 -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 +823 -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 +258 -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
@@ -0,0 +1,4 @@
1
+ export * from "./gasless.js";
2
+ export * from "./gasless-client.js";
3
+ export * from "./gasless-store.js";
4
+ export * from "./gasless-operators.js";
@@ -0,0 +1,227 @@
1
+ import { Contract, Interface, verifyTypedData, type Provider } from "ethers";
2
+ import type { LimitOrderConfigResponse, LimitOrderRecord } from "./types.js";
3
+ import type {
4
+ GaslessDeployment,
5
+ GaslessOrder,
6
+ GaslessRoute,
7
+ } from "./gasless-types.js";
8
+ import {
9
+ GASLESS_OPERATOR_ROLE,
10
+ GASLESS_SWITCH_ABI,
11
+ GaslessError,
12
+ gaslessAddress,
13
+ gaslessUint,
14
+ getGaslessSigningParams,
15
+ readGaslessChainState,
16
+ sameAddress,
17
+ validateGaslessConfig,
18
+ } from "./gasless.js";
19
+
20
+ /** Never infer gasless from a symbol, short deadline, orderType label, or legacy array. */
21
+ export function classifySwitchOrder(
22
+ order: Pick<LimitOrderRecord, "limitOrderContract">,
23
+ config: LimitOrderConfigResponse,
24
+ deployment: GaslessDeployment,
25
+ ): "gasless" | "limit-order" | "unsupported" {
26
+ validateGaslessConfig(config, deployment);
27
+ if (sameAddress(order.limitOrderContract, deployment.contract))
28
+ return "gasless";
29
+ if (sameAddress(order.limitOrderContract, config.limitOrderContract))
30
+ return "limit-order";
31
+ return "unsupported";
32
+ }
33
+
34
+ export function selectGaslessPriorityOrders(
35
+ orders: readonly LimitOrderRecord[],
36
+ config: LimitOrderConfigResponse,
37
+ deployment: GaslessDeployment,
38
+ chainTimestamp: number,
39
+ ): LimitOrderRecord[] {
40
+ if (!Number.isSafeInteger(chainTimestamp) || chainTimestamp <= 0)
41
+ throw new GaslessError(
42
+ "INVALID_TIMESTAMP",
43
+ "A chain timestamp is required.",
44
+ );
45
+ const seen = new Set<string>();
46
+ return orders
47
+ .filter((order) => {
48
+ const key = `${order.limitOrderContract?.toLowerCase()}:${order.maker.toLowerCase()}:${order.nonce}`;
49
+ if (
50
+ classifySwitchOrder(order, config, deployment) !== "gasless" ||
51
+ order.status !== "ACTIVE" ||
52
+ !Number.isSafeInteger(order.deadline) ||
53
+ order.deadline <= chainTimestamp ||
54
+ seen.has(key)
55
+ )
56
+ return false;
57
+ seen.add(key);
58
+ return true;
59
+ })
60
+ .sort((a, b) => a.deadline - b.deadline || a.nonce - b.nonce);
61
+ }
62
+
63
+ /** Gasless V2 fixes the excess side from the maker's signed fee mode. */
64
+ export function gaslessExcessOnInput(feeOnOutput: boolean): boolean {
65
+ if (typeof feeOnOutput !== "boolean")
66
+ throw new GaslessError(
67
+ "INVALID_FEE_MODE",
68
+ "The signed fee mode is required.",
69
+ );
70
+ return !feeOnOutput;
71
+ }
72
+
73
+ /** Nominal cap before payout transfer tax. Gas is part of this payout, never an add-on. */
74
+ export function cappedGaslessPayout(
75
+ availableSurplus: bigint,
76
+ afterFeeBase: bigint,
77
+ ): bigint {
78
+ if (afterFeeBase <= 0n || availableSurplus <= 0n) return 0n;
79
+ const cap = (afterFeeBase * 5n) / 10000n;
80
+ return availableSurplus < cap ? availableSurplus : cap;
81
+ }
82
+
83
+ /** Use measured after-tax payout valued via an executable payout-token/native route. */
84
+ export function assessGaslessOperatorProfit(
85
+ payoutNative: bigint,
86
+ gasLimit: bigint,
87
+ gasPrice: bigint,
88
+ minimumNetNative = 0n,
89
+ ) {
90
+ if (
91
+ payoutNative < 0n ||
92
+ gasLimit <= 0n ||
93
+ gasPrice <= 0n ||
94
+ minimumNetNative < 0n
95
+ )
96
+ throw new GaslessError("INVALID_GAS", "Invalid payout or full gas budget.");
97
+ const gasBudgetNative = gasLimit * gasPrice;
98
+ const netNative = payoutNative - gasBudgetNative;
99
+ return {
100
+ eligible: netNative > 0n && netNative >= minimumNetNative,
101
+ gasBudgetNative,
102
+ netNative,
103
+ };
104
+ }
105
+
106
+ export async function assertGaslessOperator(
107
+ provider: Provider,
108
+ deployment: GaslessDeployment,
109
+ operator: string,
110
+ ): Promise<void> {
111
+ const state = await readGaslessChainState(provider, deployment);
112
+ const c = new Contract(deployment.contract, GASLESS_SWITCH_ABI, provider);
113
+ if (
114
+ !(await c.hasRole(GASLESS_OPERATOR_ROLE, gaslessAddress(operator), {
115
+ blockTag: state.blockNumber,
116
+ }))
117
+ )
118
+ throw new GaslessError(
119
+ "OPERATOR_NOT_APPROVED",
120
+ "This operator is not registered on the gasless settlement contract.",
121
+ );
122
+ }
123
+
124
+ /** Encode already-built execution routes. This does NOT quote, simulate, or establish profitability. */
125
+ export function encodeGaslessFill(input: {
126
+ deployment: GaslessDeployment;
127
+ order: GaslessOrder;
128
+ signature: string;
129
+ routes: readonly GaslessRoute[];
130
+ /** Actual maker -> settlement balance delta, required for an input-fee fill. */
131
+ receivedInput?: string;
132
+ chainTimestamp: number;
133
+ }): { to: string; data: string; value: string } {
134
+ const { deployment, order, signature, routes } = input;
135
+ if (
136
+ !Number.isSafeInteger(order.nonce) ||
137
+ order.nonce < 0 ||
138
+ !Number.isSafeInteger(order.deadline) ||
139
+ !Number.isSafeInteger(input.chainTimestamp) ||
140
+ order.deadline <= input.chainTimestamp ||
141
+ order.deadline === 0 ||
142
+ typeof order.unwrapOutput !== "boolean" ||
143
+ (order.unwrapOutput &&
144
+ !sameAddress(order.tokenOut, deployment.wrappedNative))
145
+ )
146
+ throw new GaslessError(
147
+ "INVALID_ORDER",
148
+ "Invalid or expired gasless order.",
149
+ );
150
+ [order.maker, order.tokenIn, order.tokenOut, order.recipient].forEach((a) =>
151
+ gaslessAddress(a),
152
+ );
153
+ gaslessAddress(order.partnerAddress, true);
154
+ gaslessUint(order.minAmountOut);
155
+ if (
156
+ sameAddress(order.tokenIn, order.tokenOut) ||
157
+ sameAddress(order.recipient, deployment.contract)
158
+ )
159
+ throw new GaslessError(
160
+ "INVALID_ORDER",
161
+ "Invalid gasless token pair or recipient.",
162
+ );
163
+ const { domain, types } = getGaslessSigningParams(deployment);
164
+ if (
165
+ !sameAddress(verifyTypedData(domain, types, order, signature), order.maker)
166
+ )
167
+ throw new GaslessError(
168
+ "INVALID_SIGNATURE",
169
+ "Gasless signature does not match the maker.",
170
+ );
171
+ let total = 0n;
172
+ if (!routes.length || routes.length > 16)
173
+ throw new GaslessError("INVALID_ROUTE", "Execution routes are required.");
174
+ for (const route of routes) {
175
+ const amount = gaslessUint(String(route.amountIn));
176
+ total += amount;
177
+ if (
178
+ !route.hops.length ||
179
+ route.hops.length > 8 ||
180
+ !sameAddress(route.hops[0].tokenIn, order.tokenIn) ||
181
+ !sameAddress(route.hops[route.hops.length - 1].tokenOut, order.tokenOut)
182
+ )
183
+ throw new GaslessError(
184
+ "INVALID_ROUTE",
185
+ "Route endpoints do not match the order.",
186
+ );
187
+ let previous = order.tokenIn;
188
+ for (const hop of route.hops) {
189
+ if (!sameAddress(hop.tokenIn, previous) || !hop.legs.length)
190
+ throw new GaslessError(
191
+ "INVALID_ROUTE",
192
+ "Disconnected execution route.",
193
+ );
194
+ previous = gaslessAddress(hop.tokenOut);
195
+ for (const leg of hop.legs) {
196
+ gaslessAddress(leg.adapter);
197
+ gaslessUint(String(leg.amountIn));
198
+ }
199
+ }
200
+ }
201
+ const amountIn = gaslessUint(order.amountIn);
202
+ if (order.feeOnOutput) {
203
+ if (total !== amountIn)
204
+ throw new GaslessError(
205
+ "INVALID_ROUTE",
206
+ "Output-fee gasless orders route the full signed input.",
207
+ );
208
+ } else {
209
+ const received = gaslessUint(input.receivedInput);
210
+ const available = received - (received * 10n) / 10000n;
211
+ if (received > amountIn || total > available)
212
+ throw new GaslessError(
213
+ "INVALID_ROUTE",
214
+ "Input-fee routes exceed the measured after-fee input.",
215
+ );
216
+ }
217
+ return {
218
+ to: deployment.contract,
219
+ data: new Interface(GASLESS_SWITCH_ABI).encodeFunctionData("fillOrder", [
220
+ order,
221
+ signature,
222
+ routes,
223
+ gaslessExcessOnInput(order.feeOnOutput),
224
+ ]),
225
+ value: "0",
226
+ };
227
+ }
@@ -0,0 +1,137 @@
1
+ import type { LimitOrderNetwork } from "./limit-orders.js";
2
+ import type {
3
+ GaslessPendingStore,
4
+ PendingGaslessOrder,
5
+ } from "./gasless-types.js";
6
+ import {
7
+ GASLESS_DEPLOYMENTS,
8
+ GaslessError,
9
+ gaslessAddress,
10
+ gaslessUint,
11
+ sameAddress,
12
+ } from "./gasless.js";
13
+
14
+ export interface GaslessStorage {
15
+ getItem(key: string): string | null;
16
+ setItem(key: string, value: string): void;
17
+ removeItem(key: string): void;
18
+ }
19
+
20
+ export interface GaslessLockManager {
21
+ request<T>(name: string, callback: () => T | PromiseLike<T>): Promise<T>;
22
+ }
23
+
24
+ function identity(p: PendingGaslessOrder): string {
25
+ return `${p.chainId}:${p.contract.toLowerCase()}:${p.order.maker.toLowerCase()}:${p.order.nonce}`;
26
+ }
27
+
28
+ /** Invalid storage must block new orders rather than silently discarding a live signature. */
29
+ export function parseGaslessPending(
30
+ value: string,
31
+ network: LimitOrderNetwork,
32
+ maker: string,
33
+ ): PendingGaslessOrder {
34
+ try {
35
+ const p = JSON.parse(value) as PendingGaslessOrder;
36
+ if (
37
+ p.network !== network ||
38
+ p.chainId !== GASLESS_DEPLOYMENTS[network].chainId ||
39
+ !sameAddress(p.order.maker, maker) ||
40
+ !Number.isSafeInteger(p.order.nonce) ||
41
+ p.order.nonce < 0 ||
42
+ !Number.isSafeInteger(p.order.deadline) ||
43
+ p.order.deadline <= 0 ||
44
+ typeof p.order.feeOnOutput !== "boolean" ||
45
+ typeof p.order.unwrapOutput !== "boolean" ||
46
+ !["signing", "submitting", "submitted", "uncertain"].includes(p.state) ||
47
+ (p.state !== "signing" && !p.signature) ||
48
+ (p.signature !== undefined && !/^0x[\da-f]{130}$/i.test(p.signature))
49
+ )
50
+ throw Error();
51
+ [
52
+ p.contract,
53
+ p.order.maker,
54
+ p.order.tokenIn,
55
+ p.order.tokenOut,
56
+ p.order.recipient,
57
+ ].forEach((a) => gaslessAddress(a));
58
+ gaslessAddress(p.order.partnerAddress, true);
59
+ gaslessUint(p.order.amountIn);
60
+ gaslessUint(p.order.minAmountOut);
61
+ return p;
62
+ } catch {
63
+ throw new GaslessError(
64
+ "PENDING_STORAGE_INVALID",
65
+ "Saved gasless order data is invalid. Recover its status before starting another swap.",
66
+ );
67
+ }
68
+ }
69
+
70
+ /** Browser store serializes tabs using Web Locks and persists orders across reloads. */
71
+ export function createGaslessBrowserStore(
72
+ network: LimitOrderNetwork,
73
+ makerInput: string,
74
+ options: {
75
+ storage?: GaslessStorage;
76
+ locks?: GaslessLockManager;
77
+ } = {},
78
+ ): GaslessPendingStore {
79
+ const maker = gaslessAddress(makerInput);
80
+ const storage = options.storage ?? globalThis.localStorage;
81
+ const locks = options.locks ?? globalThis.navigator?.locks;
82
+ if (!storage || !locks)
83
+ throw new GaslessError(
84
+ "PENDING_STORAGE_UNAVAILABLE",
85
+ "Durable storage and Web Locks are required. Supply an atomic application store in this environment.",
86
+ );
87
+ const key = `switch:sdk:gasless-pending:${GASLESS_DEPLOYMENTS[network].chainId}:${maker.toLowerCase()}`;
88
+ const load = () => {
89
+ const value = storage.getItem(key);
90
+ return value === null ? null : parseGaslessPending(value, network, maker);
91
+ };
92
+ const check = (p: PendingGaslessOrder) =>
93
+ parseGaslessPending(JSON.stringify(p), network, maker);
94
+ return {
95
+ async load() {
96
+ return load();
97
+ },
98
+ async reserve(pending) {
99
+ await locks.request(key, () => {
100
+ if (load())
101
+ throw new GaslessError(
102
+ "ORDER_PENDING",
103
+ "Another gasless order is pending in this browser.",
104
+ );
105
+ storage.setItem(key, JSON.stringify(check(pending)));
106
+ });
107
+ },
108
+ async update(pending) {
109
+ await locks.request(key, () => {
110
+ const current = load();
111
+ if (!current || identity(current) !== identity(pending))
112
+ throw new GaslessError(
113
+ "PENDING_ORDER_CHANGED",
114
+ "Saved pending order changed.",
115
+ );
116
+ if (JSON.stringify(current.order) !== JSON.stringify(pending.order))
117
+ throw new GaslessError(
118
+ "PENDING_ORDER_CHANGED",
119
+ "Cannot change a pending order's signed fields.",
120
+ );
121
+ if (current.signature && current.signature !== pending.signature)
122
+ throw new GaslessError(
123
+ "PENDING_ORDER_CHANGED",
124
+ "Cannot replace a saved signature.",
125
+ );
126
+ storage.setItem(key, JSON.stringify(check(pending)));
127
+ });
128
+ },
129
+ async release(pending) {
130
+ await locks.request(key, () => {
131
+ const current = load();
132
+ if (current && identity(current) === identity(pending))
133
+ storage.removeItem(key);
134
+ });
135
+ },
136
+ };
137
+ }
@@ -0,0 +1,258 @@
1
+ import type {
2
+ BestPathResponse,
3
+ LimitOrderParams,
4
+ LimitOrderRecord,
5
+ } from "./types.js";
6
+ import type { LimitOrderNetwork } from "./limit-orders.js";
7
+
8
+ export interface GaslessDeployment {
9
+ network: LimitOrderNetwork;
10
+ chainId: number;
11
+ contract: string;
12
+ router: string;
13
+ wrappedNative: string;
14
+ explorer: string;
15
+ }
16
+
17
+ export type GaslessEligibilityReason =
18
+ | "READY"
19
+ | "NOT_REQUESTED"
20
+ | "EXECUTION_UNVERIFIED"
21
+ | "PAYOUT_UNVALUED"
22
+ | "GAS_NOT_COVERED"
23
+ | "INSUFFICIENT_SAFETY_MARGIN"
24
+ | "OPERATOR_GAS_UNAVAILABLE"
25
+ | "NO_APPROVED_OPERATOR"
26
+ | "SLIPPAGE_BUFFER_TOO_SMALL"
27
+ | "STALE_PREVIEW";
28
+
29
+ /** Server recommendation; all native Wei values are decimal strings. */
30
+ export interface GaslessEligibility {
31
+ policyVersion: 1;
32
+ status: "eligible" | "ineligible" | "unavailable" | "not_checked";
33
+ eligible: boolean;
34
+ recommendation: "gasless" | "regular";
35
+ reasonCode: GaslessEligibilityReason;
36
+ reason: string;
37
+ nativeSymbol: "PLS" | "ETH";
38
+ protocolFeeBps: 10;
39
+ maxOperatorPayoutBps: 5;
40
+ requiredGasCoverageBps: 20000;
41
+ payoutValuationHaircutBps: 1000;
42
+ minimumSlippageBps: 50;
43
+ /** Registration and a funded wallet do not prove a worker is online. */
44
+ operatorLivenessVerified: false;
45
+ /** Milliseconds since the Unix epoch. */
46
+ checkedAt: number;
47
+ /** Milliseconds since the Unix epoch. */
48
+ expiresAt: number;
49
+ economics?: {
50
+ estimatedGasUnits: string;
51
+ gasLimit: string;
52
+ gasPriceWei: string;
53
+ estimatedGasCostNativeWei: string;
54
+ estimatedGasCostNative: string;
55
+ operatorPayoutNativeWei: string;
56
+ operatorPayoutNative: string;
57
+ gasBudgetNativeWei: string;
58
+ gasBudgetNative: string;
59
+ conservativeGasBudgetNativeWei: string;
60
+ conservativeGasBudgetNative: string;
61
+ stressedOperatorPayoutNativeWei: string;
62
+ requiredOperatorPayoutNativeWei: string;
63
+ requiredOperatorPayoutNative: string;
64
+ payoutToGasCoverageBps: string;
65
+ /** Signed: negative values mean the payout does not cover the budget. */
66
+ netAfterGasBudgetNativeWei: string;
67
+ shortfallNativeWei: string;
68
+ gasCovered: boolean;
69
+ safetyMarginCovered: boolean;
70
+ };
71
+ operator?: {
72
+ address: string;
73
+ nativeBalanceWei: string;
74
+ gasAvailable: boolean;
75
+ };
76
+ }
77
+
78
+ /** Amounts are uint256 decimal strings; timestamps are Unix seconds except checkedAt. */
79
+ export interface GaslessExecutionPreview {
80
+ status: "ready";
81
+ version: 2;
82
+ settlementVersion: 2;
83
+ maker: string;
84
+ chainId: number;
85
+ contract: string;
86
+ tokenIn: string;
87
+ tokenOut: string;
88
+ amountIn: string;
89
+ recipient: string;
90
+ unwrapOutput: boolean;
91
+ feeOnOutput: boolean;
92
+ excessOnInput: boolean;
93
+ payoutToken: string;
94
+ routeHash: string;
95
+ quoteBlockNumber: number;
96
+ /** Milliseconds since the Unix epoch. */
97
+ checkedAt: number;
98
+ slippageBps: number;
99
+ expectedOutputAmount: string;
100
+ minAmountOut: string;
101
+ recipientAmountOut: string;
102
+ /** Actual payout received after any transfer tax. */
103
+ operatorPayout: string;
104
+ nominalOperatorPayout: string;
105
+ payoutCapBase: string;
106
+ operatorPayoutNative: string;
107
+ estimatedGas: string;
108
+ gasLimit: string;
109
+ gasPriceWei: string;
110
+ gasBudgetNative: string;
111
+ gasPriceBoostBps: number;
112
+ gasLimitBps: number;
113
+ headroomBps: number;
114
+ eligible: boolean;
115
+ reason?: string;
116
+ /** Additive API metadata. A ready simulation can still be economically ineligible. */
117
+ eligibility?: GaslessEligibility;
118
+ }
119
+
120
+ export type GaslessPreviewResult =
121
+ | GaslessExecutionPreview
122
+ | {
123
+ status: "unavailable";
124
+ reason: string;
125
+ eligibility?: GaslessEligibility;
126
+ };
127
+
128
+ export interface GaslessQuoteResponse extends BestPathResponse {
129
+ protocolFeeBps: number;
130
+ feeOnOutput: boolean;
131
+ quoteGasPrice: string;
132
+ quoteBlockNumber: number;
133
+ gaslessExecution: GaslessPreviewResult;
134
+ }
135
+
136
+ export interface GaslessTrade {
137
+ maker: string;
138
+ tokenIn: string;
139
+ /** Native sentinel is supported on output; signed orders use wrapped native. */
140
+ tokenOut: string;
141
+ amountIn: string;
142
+ recipient?: string;
143
+ partnerAddress?: string;
144
+ /** User price tolerance only. It never increases the contract's payout cap. 0–100. */
145
+ slippageBps?: number;
146
+ }
147
+
148
+ export interface GaslessTaxInfo {
149
+ isTaxToken: boolean;
150
+ buyTaxBps: number;
151
+ sellTaxBps: number;
152
+ }
153
+
154
+ export interface GaslessChainState {
155
+ checkedAt: number;
156
+ blockNumber: number;
157
+ blockTimestamp: number;
158
+ rawGasPriceWei: string;
159
+ }
160
+
161
+ export interface PreparedGaslessSwap {
162
+ deployment: GaslessDeployment;
163
+ trade: Required<GaslessTrade>;
164
+ quote: GaslessQuoteResponse;
165
+ preview: GaslessExecutionPreview;
166
+ feeOnOutput: boolean;
167
+ expectedOutput: string;
168
+ minAmountOut: string;
169
+ payoutToken: string;
170
+ operatorPayout: string;
171
+ gasPriceWei: string;
172
+ gasLimit: string;
173
+ gasBudgetNative: string;
174
+ chainState: GaslessChainState;
175
+ }
176
+
177
+ export type GaslessUnavailableCode =
178
+ | "NATIVE_INPUT_REQUIRES_WRAP"
179
+ | "CONFIGURATION_CHANGED"
180
+ | "TAX_CHECK_UNAVAILABLE"
181
+ | "QUOTE_UNAVAILABLE"
182
+ | "PREVIEW_UNAVAILABLE"
183
+ | "STALE_QUOTE"
184
+ | "INVALID_PREVIEW"
185
+ | "SERVER_INELIGIBLE"
186
+ | "INSUFFICIENT_OPERATOR_PAYOUT";
187
+
188
+ export type GaslessQuoteDecision =
189
+ | {
190
+ eligible: true;
191
+ prepared: PreparedGaslessSwap;
192
+ eligibility?: GaslessEligibility;
193
+ }
194
+ | {
195
+ eligible: false;
196
+ code: GaslessUnavailableCode;
197
+ reason: string;
198
+ retryable: boolean;
199
+ /** Server economics are available even when this swap cannot use gasless. */
200
+ eligibility?: GaslessEligibility;
201
+ };
202
+
203
+ /** The API currently stores nonces as safe JSON integers, even though Solidity uses uint256. */
204
+ export interface GaslessOrder extends LimitOrderParams {}
205
+
206
+ export interface SignedGaslessOrder extends GaslessOrder {
207
+ limitOrderContract: string;
208
+ signature: string;
209
+ }
210
+
211
+ export interface PendingGaslessOrder {
212
+ network: LimitOrderNetwork;
213
+ chainId: number;
214
+ contract: string;
215
+ order: GaslessOrder;
216
+ state: "signing" | "submitting" | "submitted" | "uncertain";
217
+ signature?: string;
218
+ }
219
+
220
+ /** Implement atomically, scoped to network + maker. Persist before opening the wallet. */
221
+ export interface GaslessPendingStore {
222
+ load(): Promise<PendingGaslessOrder | null>;
223
+ /** Must reject if another pending order already occupies this maker's slot. */
224
+ reserve(pending: PendingGaslessOrder): Promise<void>;
225
+ /** Must update only the same contract + maker + nonce; preserve the signature. */
226
+ update(pending: PendingGaslessOrder): Promise<void>;
227
+ /** Must release only the matching order. Never clear a different tab's order. */
228
+ release(pending: PendingGaslessOrder): Promise<void>;
229
+ }
230
+
231
+ export type GaslessOrderOutcome =
232
+ | { status: "pending"; pending: PendingGaslessOrder }
233
+ | {
234
+ status: "filled";
235
+ pending: PendingGaslessOrder;
236
+ record: LimitOrderRecord;
237
+ settlementTxUrl?: string;
238
+ }
239
+ | {
240
+ status: "cancelled";
241
+ pending: PendingGaslessOrder;
242
+ record: LimitOrderRecord;
243
+ }
244
+ | { status: "closed"; pending: PendingGaslessOrder; reason: string };
245
+
246
+ export interface GaslessRoute {
247
+ amountIn: string | bigint;
248
+ hops: {
249
+ tokenIn: string;
250
+ tokenOut: string;
251
+ legs: {
252
+ adapter: string;
253
+ amountIn: string | bigint;
254
+ fee: number;
255
+ data: string;
256
+ }[];
257
+ }[];
258
+ }