@orbs-network/spot-react 1.1.41 → 1.1.43

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 (40) hide show
  1. package/README.md +413 -257
  2. package/dist/spot-react/package.json.d.ts +1 -1
  3. package/dist/spot-react/src/hooks/order-hooks.d.ts.map +1 -1
  4. package/dist/spot-react/src/hooks/use-cancel-order.d.ts +2 -0
  5. package/dist/spot-react/src/hooks/use-cancel-order.d.ts.map +1 -1
  6. package/dist/spot-react/src/hooks/use-form-data.d.ts +10 -31
  7. package/dist/spot-react/src/hooks/use-form-data.d.ts.map +1 -1
  8. package/dist/spot-react/src/hooks/use-history-order.d.ts.map +1 -1
  9. package/dist/spot-react/src/hooks/use-repermit-data.d.ts +7 -0
  10. package/dist/spot-react/src/hooks/use-repermit-data.d.ts.map +1 -0
  11. package/dist/spot-react/src/hooks/use-repermit-order-data.d.ts +14 -31
  12. package/dist/spot-react/src/hooks/use-repermit-order-data.d.ts.map +1 -1
  13. package/dist/spot-react/src/hooks/use-submit-order-panel.d.ts +3 -1
  14. package/dist/spot-react/src/hooks/use-submit-order-panel.d.ts.map +1 -1
  15. package/dist/spot-react/src/hooks/use-submit-order.d.ts.map +1 -1
  16. package/dist/spot-react/src/index.d.ts +2 -1
  17. package/dist/spot-react/src/index.d.ts.map +1 -1
  18. package/dist/spot-react/src/spot-context.d.ts.map +1 -1
  19. package/dist/spot-react/src/types.d.ts +1 -2
  20. package/dist/spot-react/src/types.d.ts.map +1 -1
  21. package/dist/spot-react.js +2456 -2480
  22. package/dist/spot-react.umd.cjs +7 -7
  23. package/dist/spot-ui/src/lib/analytics.d.ts +7 -5
  24. package/dist/spot-ui/src/lib/analytics.d.ts.map +1 -1
  25. package/dist/spot-ui/src/lib/build-repermit-order-data.d.ts +21 -41
  26. package/dist/spot-ui/src/lib/build-repermit-order-data.d.ts.map +1 -1
  27. package/dist/spot-ui/src/lib/consts.d.ts +3 -2
  28. package/dist/spot-ui/src/lib/consts.d.ts.map +1 -1
  29. package/dist/spot-ui/src/lib/lib.d.ts +15 -2
  30. package/dist/spot-ui/src/lib/lib.d.ts.map +1 -1
  31. package/dist/spot-ui/src/lib/orders/index.d.ts +6 -3
  32. package/dist/spot-ui/src/lib/orders/index.d.ts.map +1 -1
  33. package/dist/spot-ui/src/lib/orders/v2-orders.d.ts.map +1 -1
  34. package/dist/spot-ui/src/lib/types.d.ts +16 -16
  35. package/dist/spot-ui/src/lib/types.d.ts.map +1 -1
  36. package/dist/spot-ui/src/lib/utils.d.ts +1 -1
  37. package/dist/spot-ui/src/lib/utils.d.ts.map +1 -1
  38. package/package.json +2 -2
  39. package/dist/spot-react/src/hooks/use-addresses.d.ts +0 -4
  40. package/dist/spot-react/src/hooks/use-addresses.d.ts.map +0 -1
package/README.md CHANGED
@@ -2,383 +2,539 @@
2
2
 
3
3
  React SDK for building TWAP, Limit, Stop-Loss, and Take-Profit order interfaces on top of the [Orbs Spot protocol](https://www.orbs.com/).
4
4
 
5
+ `spot-react` owns Spot order state and exposes it through `SpotProvider` and `useSpot()`. The host DEX remains responsible for swap-form state, wallet access, components, styling, translations, routing, and modal shells.
6
+
7
+ For the complete integration workflow, see the [Spot React integration skill](https://github.com/orbs-network/spot-ui/tree/master/skills/spot-react-integration) and the [reference implementation](https://github.com/orbs-network/spot-ui/blob/master/apps/web/components/spot/spot-form.tsx).
8
+
9
+ ## Before You Start
10
+
11
+ Every DEX needs a member of the exported `Partners` enum and a server-side Orbs Spot configuration for its supported chains. The SDK fetches contract and adapter addresses from that configuration. If the DEX is not configured yet, contact [@dTWAPSupportGroup](https://t.me/dTWAPSupportGroup) before integrating.
12
+
13
+ ### RePermit configuration security
14
+
15
+ The `/config` endpoint is a security boundary. The SDK trusts successful JSON responses without client-side schema or contract-identity validation. It uses `domain.verifyingContract` as the ERC-20 approval spender and the v2 cancellation contract, and uses the returned adapter, reactor, and executor when constructing orders. Deployments must use the trusted Orbs endpoint over TLS, and contract-address changes require explicit approval from the protocol/security owner.
16
+
17
+ `SpotProvider` starts a shared configuration query as soon as the partner and chain are known. Concurrent consumers reuse the same query, successful responses remain cached for that partner/chain/environment, and failed requests are retried twice. `useRePermitData()` exposes the query state and `refetch`; the standard submit panel exposes the same recovery action as `useSpot().submitOrderButton.retry`.
18
+
19
+ The completed integration should:
20
+
21
+ - keep tokens, typed amount, balances, USD prices, and quote state in the DEX's existing swap store or context;
22
+ - provide all five `walletInteractions` methods using the DEX's existing wallet stack;
23
+ - render TWAP, Limit, Stop-Loss, and Take-Profit entry points using DEX-native navigation and components;
24
+ - use `@orbs-network/swap-ui` for the order review/progress flow inside the DEX's modal shell;
25
+ - render order history and cancellation inside `SpotProvider` scope or through a context-preserving portal.
26
+
5
27
  ## Installation
6
28
 
29
+ Install the latest Spot packages with the host application's package manager. Do not mix lockfiles.
30
+
7
31
  ```bash
8
- npm install @orbs-network/spot-react
32
+ npm install @orbs-network/spot-react@latest @orbs-network/swap-ui@latest
33
+ # or: pnpm add @orbs-network/spot-react@latest @orbs-network/swap-ui@latest
34
+ # or: yarn add @orbs-network/spot-react@latest @orbs-network/swap-ui@latest
9
35
  ```
10
36
 
37
+ If migrating from `@orbs-network/twap-ui`, remove it before installing the packages above.
38
+
11
39
  ### Peer Dependencies
12
40
 
13
- These must be installed in your application:
41
+ The host application must also provide:
14
42
 
15
43
  ```bash
16
44
  npm install @tanstack/react-query bignumber.js react-error-boundary zustand react react-dom
17
45
  ```
18
46
 
19
- | Package | Version |
20
- | ---------------------- | ---------- |
21
- | `@tanstack/react-query`| `^5.90.12` |
22
- | `bignumber.js` | `^9.3.1` |
23
- | `react-error-boundary` | `^6.0.0` |
24
- | `zustand` | `^5.0.9` |
25
- | `react` | `^18 \|\| ^19` |
26
- | `react-dom` | `^18 \|\| ^19` |
47
+ | Package | Version |
48
+ | --- | --- |
49
+ | `@tanstack/react-query` | `^5.90.12` |
50
+ | `bignumber.js` | `^9.3.1` |
51
+ | `react-error-boundary` | `^6.0.0` |
52
+ | `zustand` | `^5.0.9` |
53
+ | `react` | `^18 \|\| ^19` |
54
+ | `react-dom` | `^18 \|\| ^19` |
55
+
56
+ `viem` is not a dependency. Adapt the wallet library already used by the DEX through `walletInteractions`.
57
+
58
+ ## Integration Model
27
59
 
28
- ## Quick Start
60
+ Keep the DEX swap form as the source of truth. Pass the following adapted values directly to `SpotProvider`:
61
+
62
+ | Value | Expected shape |
63
+ | --- | --- |
64
+ | Selected tokens | `Token` objects with `address`, `symbol`, `decimals`, and `logoUrl` |
65
+ | Typed source amount | User-facing decimal string, for example `"1.25"` |
66
+ | Quote output | Raw destination-token amount for the current typed input |
67
+ | Balances | Raw integer strings |
68
+ | USD prices | USD value of exactly one whole source/destination token |
69
+ | Chain and account | Values from the connected wallet/account state |
70
+
71
+ If several Spot components need the same DEX-owned values, expose a small DEX adapter context. Do not copy the swap state into a parallel Spot store, prop-drill long value lists, or create a hook whose only purpose is forwarding props to `SpotProvider`.
72
+
73
+ Memoize objects with `useMemo` and functions with `useCallback`. In particular, keep stable identities for tokens, `marketReferencePrice`, `walletInteractions`, and `callbacks`.
74
+
75
+ ## Provider Setup
29
76
 
30
77
  ```tsx
78
+ import { useMemo } from "react";
31
79
  import {
32
- SpotProvider,
33
- useSpot,
34
80
  Module,
35
81
  Partners,
82
+ SpotProvider,
83
+ type Callbacks,
84
+ type MarketReferencePrice,
85
+ type Token,
36
86
  type WalletInteractions,
37
87
  } from "@orbs-network/spot-react";
38
88
 
39
- function App() {
89
+ function SpotOrderForm({ module }: { module: Module }) {
90
+ // Read these from the DEX's existing swap and wallet state.
91
+ const {
92
+ account,
93
+ chainId,
94
+ inputCurrency,
95
+ outputCurrency,
96
+ inputBalance,
97
+ outputBalance,
98
+ typedInputAmount,
99
+ quotedInputAmount,
100
+ quoteOutputRaw,
101
+ isQuoteLoading,
102
+ inputUsdPrice,
103
+ outputUsdPrice,
104
+ refetchBalances,
105
+ dexWallet,
106
+ } = useDexSpotAdapter();
107
+
108
+ const srcToken = useMemo<Token | undefined>(() => {
109
+ if (!inputCurrency) return undefined;
110
+ return {
111
+ address: inputCurrency.address,
112
+ symbol: inputCurrency.symbol,
113
+ decimals: inputCurrency.decimals,
114
+ logoUrl: inputCurrency.logoUrl,
115
+ };
116
+ }, [inputCurrency]);
117
+
118
+ const dstToken = useMemo<Token | undefined>(() => {
119
+ if (!outputCurrency) return undefined;
120
+ return {
121
+ address: outputCurrency.address,
122
+ symbol: outputCurrency.symbol,
123
+ decimals: outputCurrency.decimals,
124
+ logoUrl: outputCurrency.logoUrl,
125
+ };
126
+ }, [outputCurrency]);
127
+
128
+ const marketReferencePrice = useMemo<MarketReferencePrice>(() => {
129
+ const shouldQuote = Boolean(
130
+ typedInputAmount && inputCurrency && outputCurrency,
131
+ );
132
+ const isStale = shouldQuote && typedInputAmount !== quotedInputAmount;
133
+ const value = !shouldQuote || isStale ? undefined : quoteOutputRaw;
134
+ const isLoading = shouldQuote && (isStale || isQuoteLoading);
135
+
136
+ return {
137
+ value,
138
+ isLoading,
139
+ noLiquidity: shouldQuote && !isLoading && !value,
140
+ };
141
+ }, [
142
+ inputCurrency,
143
+ isQuoteLoading,
144
+ outputCurrency,
145
+ quoteOutputRaw,
146
+ quotedInputAmount,
147
+ typedInputAmount,
148
+ ]);
149
+
150
+ const walletInteractions = useMemo<WalletInteractions>(
151
+ () => createWalletInteractions(dexWallet),
152
+ [dexWallet],
153
+ );
154
+
155
+ const callbacks = useMemo<Callbacks>(
156
+ () => ({
157
+ onWrapSuccess: () => refetchBalances(),
158
+ onOrderCreated: () => refetchBalances(),
159
+ onOrderFilled: () => refetchBalances(),
160
+ onOrdersProgressUpdate: () => refetchBalances(),
161
+ onCancelOrderSuccess: () => refetchBalances(),
162
+ }),
163
+ [refetchBalances],
164
+ );
165
+
40
166
  return (
41
167
  <SpotProvider
42
- chainId={137} // Polygon
43
- walletInteractions={walletInteractions} // Wallet interaction handlers
44
- account={walletAddress} // Connected wallet address
45
- appId="my-dex" // Optional analytics app ID
46
- partner={Partners.Quick} // DEX partner
47
- module={Module.TWAP} // Order type
48
- typedInputAmount={inputAmount} // User-typed amount (string)
49
- priceProtection={0.5} // Slippage tolerance %
50
- minChunkSizeUsd={5} // Min chunk size in USD
51
- srcToken={srcToken} // { address, symbol, decimals, logoUrl }
168
+ partner={Partners.Quick}
169
+ module={module}
170
+ typedInputAmount={typedInputAmount}
171
+ priceProtection={3}
172
+ minChunkSizeUsd={5}
173
+ marketReferencePrice={marketReferencePrice}
174
+ walletInteractions={walletInteractions}
175
+ chainId={chainId}
176
+ account={account}
177
+ srcToken={srcToken}
52
178
  dstToken={dstToken}
53
- srcBalance={srcBalanceWei} // Balance in wei (string)
54
- dstBalance={dstBalanceWei}
55
- srcUsd1Token={srcUsdPrice} // USD price of 1 token (string)
56
- dstUsd1Token={dstUsdPrice}
57
- marketReferencePrice={marketPrice} // { value?: string, isLoading?: boolean }
58
- callbacks={callbacks} // Lifecycle event handlers
59
- fees={0.25} // Fee percentage
179
+ srcBalance={inputBalance?.toString()}
180
+ dstBalance={outputBalance?.toString()}
181
+ srcUsd1Token={inputUsdPrice}
182
+ dstUsd1Token={outputUsdPrice}
183
+ callbacks={callbacks}
184
+ appId="my-dex"
185
+ fees={0.25}
60
186
  >
61
- <SwapForm />
187
+ <SpotFormContent />
62
188
  </SpotProvider>
63
189
  );
64
190
  }
65
191
  ```
66
192
 
67
- ## WalletInteractions
68
-
69
- The host app provides wallet interaction handlers via the `walletInteractions` prop. This keeps spot-react wallet-agnostic — it works with viem, ethers, web3.js, or any wallet library.
70
-
71
- ```tsx
72
- const walletInteractions: WalletInteractions = {
73
- // Wrap native token (e.g. ETH -> WETH). Wait for receipt, return tx hash.
74
- wrapNativeToken: async (amountWei) => { ... },
75
-
76
- // Approve ERC-20 token spending. Wait for receipt, return tx hash.
77
- approveToken: async ({ token, amount, spender }) => { ... },
193
+ `marketReferencePrice.value` is the DEX quote's raw destination amount for the current `typedInputAmount`, not a standalone per-token price. If the quote belongs to an older input or token pair, omit `value` and report `isLoading: true` until a current quote arrives.
78
194
 
79
- // Cancel an order on-chain. Wait for receipt, return tx hash.
80
- cancelOrder: async ({ order, contractAddress, args }) => { ... },
195
+ Use the connected wallet chain as the UI source of truth. The provider falls back internally to a supported partner chain for configuration lookups when `chainId` is absent or unsupported, so the DEX submit area must still show connect-wallet or switch-network controls and block submission.
81
196
 
82
- // Sign EIP-712 typed data for order creation. Return signature hex.
83
- signOrder: async ({ domain, types, primaryType, message, account }) => { ... },
197
+ ### SpotProvider Props
84
198
 
85
- // Read ERC-20 allowance. Return allowance as string.
86
- getAllowance: async ({ tokenAddress, spenderAddress }) => { ... },
87
- };
88
- ```
199
+ | Prop | Type | Required | Description |
200
+ | --- | --- | --- | --- |
201
+ | `partner` | `Partners` | Yes | DEX partner enum |
202
+ | `module` | `Module` | Yes | `TWAP`, `LIMIT`, `STOP_LOSS`, or `TAKE_PROFIT` |
203
+ | `typedInputAmount` | `string` | Yes | User-facing source amount from DEX state |
204
+ | `priceProtection` | `number` | Yes | Price Protection percentage; this is not swap slippage |
205
+ | `minChunkSizeUsd` | `number` | Yes | Minimum trade chunk size in USD |
206
+ | `marketReferencePrice` | `MarketReferencePrice` | Yes | `{ value?, isLoading?, noLiquidity? }` for the current DEX quote |
207
+ | `walletInteractions` | `WalletInteractions` | Yes | Five wallet methods implemented by the DEX |
208
+ | `chainId` | `number` | No | Connected wallet chain ID |
209
+ | `account` | `string` | No | Connected wallet address |
210
+ | `appId` | `string` | No | Analytics app ID |
211
+ | `srcToken` | `Token` | No | Source token metadata |
212
+ | `dstToken` | `Token` | No | Destination token metadata |
213
+ | `srcBalance` | `string` | No | Raw source-token balance |
214
+ | `dstBalance` | `string` | No | Raw destination-token balance |
215
+ | `srcUsd1Token` | `string` | No | USD value of one whole source token |
216
+ | `dstUsd1Token` | `string` | No | USD value of one whole destination token |
217
+ | `enableQueryParams` | `boolean` | No | Sync supported form state to URL query parameters |
218
+ | `callbacks` | `Callbacks` | No | Lifecycle and field-change callbacks |
219
+ | `fees` | `number` | No | Fee percentage, for example `0.25` |
220
+ | `isDev` | `boolean` | No | Use development services/configuration |
221
+ | `supportLegacyOrders` | `boolean` | No | Include supported legacy v1 orders in history |
222
+
223
+ Although balances and USD prices are optional in the TypeScript type, production integrations should pass them so validation, loading states, minimum trade size, and review details are correct.
89
224
 
90
- Each write method (`wrapNativeToken`, `approveToken`, `cancelOrder`) should wait for tx confirmation and throw if the transaction reverts.
225
+ ## WalletInteractions
91
226
 
92
- ### Example with viem/wagmi
227
+ The DEX must implement all five methods. Write methods must wait for the transaction receipt, throw when the transaction reverts, and then return the transaction hash.
93
228
 
94
229
  ```tsx
95
- import { erc20Abi } from "viem";
96
-
97
230
  const walletInteractions: WalletInteractions = {
98
231
  wrapNativeToken: async (amountWei) => {
99
- const hash = await walletClient.writeContract({
100
- abi: [{ name: "deposit", type: "function", stateMutability: "payable", inputs: [], outputs: [] }],
101
- functionName: "deposit",
102
- address: wTokenAddress,
103
- args: [],
104
- value: BigInt(amountWei),
105
- chain: walletClient.chain,
106
- account: walletClient.account!,
107
- });
108
- await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 });
109
- return hash;
232
+ const txHash = await dexWallet.wrapNativeToken(amountWei);
233
+ await dexWallet.waitForReceipt(txHash);
234
+ return txHash;
110
235
  },
111
- approveToken: async ({ token, spender }) => {
112
- const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
113
- const hash = await walletClient.writeContract({
114
- abi: erc20Abi,
115
- functionName: "approve",
116
- address: token as `0x${string}`,
117
- args: [spender as `0x${string}`, maxUint256],
118
- chain: walletClient.chain,
119
- account: walletClient.account!,
236
+
237
+ approveToken: async ({ tokenAddress, amount, spenderAddress }) => {
238
+ const txHash = await dexWallet.approveToken({
239
+ tokenAddress,
240
+ amount,
241
+ spenderAddress,
120
242
  });
121
- await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 });
122
- return hash;
243
+ await dexWallet.waitForReceipt(txHash);
244
+ return txHash;
123
245
  },
124
- cancelOrder: async ({ order, contractAddress, args }) => {
125
- const hash = await walletClient.writeContract({
126
- abi: cancelAbi, // TWAP_ABI for v1, REPERMIT_ABI for v2
246
+
247
+ cancelOrder: async ({ order, contractAddress, args, abi }) => {
248
+ const txHash = await dexWallet.writeContract({
249
+ address: contractAddress,
250
+ abi,
127
251
  functionName: "cancel",
128
- address: contractAddress as `0x${string}`,
129
- args: args as any,
130
- chain: walletClient.chain,
131
- account: walletClient.account!,
252
+ args,
132
253
  });
133
- await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 });
134
- return hash;
135
- },
136
- signOrder: async ({ domain, types, primaryType, message, account }) => {
137
- return walletClient.signTypedData({ domain, types, primaryType, message, account });
138
- },
139
- getAllowance: async ({ tokenAddress, spenderAddress }) => {
140
- const result = await publicClient.readContract({
141
- address: tokenAddress as `0x${string}`,
142
- abi: erc20Abi,
143
- functionName: "allowance",
144
- args: [walletClient.account!.address, spenderAddress as `0x${string}`],
145
- });
146
- return String(result);
254
+ await dexWallet.waitForReceipt(txHash);
255
+ return txHash;
147
256
  },
257
+
258
+ signOrder: ({ domain, types, primaryType, message, account }) =>
259
+ dexWallet.signTypedData({ domain, types, primaryType, message, account }),
260
+
261
+ getAllowance: ({ tokenAddress, spenderAddress }) =>
262
+ dexWallet.getAllowance({ tokenAddress, spenderAddress }),
148
263
  };
149
264
  ```
150
265
 
151
- ## SpotProvider Props
152
-
153
- | Prop | Type | Required | Description |
154
- | --- | --- | --- | --- |
155
- | `partner` | `Partners` | Yes | DEX partner enum |
156
- | `module` | `Module` | Yes | `TWAP`, `LIMIT`, `STOP_LOSS`, or `TAKE_PROFIT` |
157
- | `typedInputAmount` | `string` | Yes | User-typed source amount |
158
- | `priceProtection` | `number` | Yes | Slippage tolerance percentage |
159
- | `minChunkSizeUsd` | `number` | Yes | Minimum trade chunk size in USD |
160
- | `marketReferencePrice` | `MarketReferencePrice` | Yes | `{ value?: string, isLoading?: boolean, noLiquidity?: boolean }` |
161
- | `walletInteractions` | `WalletInteractions` | Yes | Wallet interaction handlers |
162
- | `chainId` | `number` | No | Connected chain ID |
163
- | `account` | `string` | No | Connected wallet address |
164
- | `appId` | `string` | No | Analytics app ID |
165
- | `srcToken` | `Token` | No | `{ address, symbol, decimals, logoUrl }` |
166
- | `dstToken` | `Token` | No | `{ address, symbol, decimals, logoUrl }` |
167
- | `srcBalance` | `string` | No | Source balance in wei |
168
- | `dstBalance` | `string` | No | Destination balance in wei |
169
- | `srcUsd1Token` | `string` | No | USD price of 1 source token |
170
- | `dstUsd1Token` | `string` | No | USD price of 1 destination token |
171
- | `callbacks` | `Callbacks` | No | Lifecycle event handlers |
172
- | `fees` | `number` | No | Fee percentage (e.g. 0.25) |
173
- | `isDev` | `boolean` | No | Enable dev mode |
174
- | `overrides` | `Overrides` | No | Initial state overrides |
266
+ | Method | Contract |
267
+ | --- | --- |
268
+ | `wrapNativeToken(amountWei)` | Wrap native currency, wait for confirmation, return tx hash |
269
+ | `approveToken({ tokenAddress, amount, spenderAddress })` | Approve the requested spender, wait for confirmation, return tx hash |
270
+ | `cancelOrder({ order, contractAddress, args, abi })` | Call `cancel` with the supplied ABI and args, wait for confirmation, return tx hash |
271
+ | `signOrder({ domain, types, primaryType, message, account })` | Sign the supplied EIP-712 data and return signature hex |
272
+ | `getAllowance({ tokenAddress, spenderAddress })` | Return the connected account's raw allowance as a string |
175
273
 
176
- ## useSpot() Hook
274
+ ## Building the Form with useSpot()
177
275
 
178
- The main hook returns all data and controls for building the UI:
276
+ Every component rendered under `SpotProvider` can call `useSpot()`:
179
277
 
180
278
  ```tsx
181
279
  const spot = useSpot();
182
280
  ```
183
281
 
184
- ### Returned panels
185
-
186
- | Panel | Description |
282
+ | Value | Key returns |
187
283
  | --- | --- |
188
- | `spot.dstTokenPanel` | `{ value, isLoading }` — estimated output amount |
189
- | `spot.tradesAmountPanel` | Total trades input, per-trade amounts, validation |
190
- | `spot.durationPanel` | Order expiry duration input |
191
- | `spot.fillDelayPanel` | Trade interval input |
192
- | `spot.limitPricePanel` | Limit price input with percentage, toggle, reset |
193
- | `spot.triggerPricePanel` | Trigger price for stop-loss / take-profit |
194
- | `spot.pricePanel` | Market price display, inversion toggle |
195
- | `spot.disclaimerPanel` | Disclaimer key string (or `undefined`) |
196
- | `spot.inputError` | Validation error `{ type, args }` (or `undefined`) |
197
- | `spot.submitOrderButton` | `{ disabled, loading }` |
198
- | `spot.orderExecutionPanel` | Full submission lifecycle (see below) |
199
- | `spot.orderHistoryPanel` | `{ orders: { all: Order[] } }` |
200
- | `spot.derivedFormData` | Computed values: amounts, prices, deadline, fees |
201
- | `spot.supportedChains` | Partner's supported chain IDs |
202
- | `spot.module` | Current `Module` enum |
203
-
204
- ### Order Execution Panel
284
+ | `dstTokenPanel` | `value`, `valueWei`, `isLoading`, `usd` |
285
+ | `tradesAmountPanel` | `totalTrades`, `maxTrades`, `onChange`, `error`, per-trade amounts, source/destination tokens |
286
+ | `durationPanel` | `duration`, input/unit callbacks, `milliseconds`, `error` |
287
+ | `fillDelayPanel` | `fillDelay`, input/unit callbacks, `milliseconds`, `error` |
288
+ | `limitPricePanel` | Raw/UI price, percentage, toggle, input/reset callbacks, tokens, USD, loading/error state |
289
+ | `triggerPricePanel` | Raw/UI trigger price, percentage, input/reset callbacks, tokens, per-chunk amounts, USD, error state |
290
+ | `pricePanel` | Inversion state/callback, source/destination tokens, market-price state |
291
+ | `disclaimerPanel` | Disclaimer translation key or `undefined` |
292
+ | `inputError` | `{ type, args }` or `undefined` |
293
+ | `submitOrderButton` | `disabled`, `loading`, configuration `error`, and `retry` |
294
+ | `orderExecutionPanel` | Submission, status, steps, errors, resets, resolved tokens, and tx hashes |
295
+ | `orderHistoryPanel` | Filtered order lists, loading state, and `refetchOrders` |
296
+ | `derivedFormData` | Review amounts, prices, timing, fees, order type, spender, and RePermit data |
297
+ | `supportedChains` | Supported chain IDs for the selected partner |
298
+ | `module` | Current `Module` |
299
+ | `refetchUntilStatusSynced` | Mutation used to reconcile cancellation status |
300
+
301
+ Child components should call `useSpot()` themselves instead of receiving hook-returned panels through intermediate props.
302
+
303
+ When `submitOrderButton.error` is set, render a translated retry label and call `submitOrderButton.retry()` instead of opening the review modal. Keep the button disabled while `loading` is true. Legacy v1 order history does not depend on RePermit configuration; v2 history, approvals, v2 cancellation, and submission resume after a successful retry.
304
+
305
+ ### Panel Visibility
306
+
307
+ | Panel | TWAP | LIMIT | STOP_LOSS | TAKE_PROFIT |
308
+ | --- | --- | --- | --- | --- |
309
+ | Trades amount | Yes | — | — | — |
310
+ | Fill delay | Yes | — | — | — |
311
+ | Duration | — | Yes | Yes | Yes |
312
+ | Limit price | Yes, optional | Always on | Optional | Optional |
313
+ | Trigger price | — | — | Yes | Yes |
314
+
315
+ Use `TimeUnit.Minutes`, `TimeUnit.Hours`, and `TimeUnit.Days` for duration and fill-delay controls. Resolve error and disclaimer keys through the DEX's i18n system.
316
+
317
+ ### Display Amounts
318
+
319
+ Raw fields such as `dstTokenPanel.valueWei`, `tradesAmountPanel.amountPerTrade`, `derivedFormData.feesAmount`, and history fill amounts are integer strings. Convert them into the DEX's native amount type before display when possible:
205
320
 
206
321
  ```tsx
207
- const {
208
- onSubmit, // Trigger order creation
209
- status, // SwapStatus: LOADING | SUCCESS | FAILED
210
- step, // Current step: WRAP | APPROVE | CREATE
211
- stepIndex, // Current step index (0-based)
212
- totalSteps, // Total number of steps
213
- parsedError, // { code, message } on failure
214
- onSwapSuccess, // Call when user acknowledges success
215
- confirmButtonLoading, // Loading state for confirm button
216
- srcToken, // Resolved source token
217
- dstToken, // Resolved destination token
218
- wrapTxHash, // Wrap transaction hash
219
- approveTxHash, // Approve transaction hash
220
- } = useSpot().orderExecutionPanel;
322
+ const amount = CurrencyAmount.fromRawAmount(
323
+ inputCurrency,
324
+ spot.tradesAmountPanel.amountPerTrade,
325
+ );
326
+
327
+ return `${amount.toSignificant()} ${inputCurrency.symbol}`;
221
328
  ```
222
329
 
223
- ### Cancel Order
330
+ Use `*UI` fields for editable text inputs or when the DEX has no amount object. Do not format raw integer strings directly for user display.
331
+
332
+ ## Submit and Progress Modal
333
+
334
+ Use `useSpot().orderExecutionPanel` for execution state, `derivedFormData` for review details, and `@orbs-network/swap-ui`'s `SwapFlow` for the creation/progress UI.
224
335
 
225
336
  ```tsx
226
- import { useCancelOrder } from "@orbs-network/spot-react";
337
+ import { SwapFlow } from "@orbs-network/swap-ui";
338
+ import { useSpot } from "@orbs-network/spot-react";
339
+
340
+ function SpotOrderFlow() {
341
+ const {
342
+ status,
343
+ parsedError,
344
+ srcToken,
345
+ dstToken,
346
+ stepIndex,
347
+ totalSteps,
348
+ } = useSpot().orderExecutionPanel;
349
+ const form = useSpot().derivedFormData;
350
+
351
+ return (
352
+ <SwapFlow
353
+ inAmount={form.srcAmountUI}
354
+ outAmount={form.dstAmountUI}
355
+ inToken={{ symbol: srcToken?.symbol, logoUrl: srcToken?.logoUrl }}
356
+ outToken={{ symbol: dstToken?.symbol, logoUrl: dstToken?.logoUrl }}
357
+ swapStatus={status}
358
+ currentStepIndex={stepIndex}
359
+ totalSteps={totalSteps}
360
+ components={{
361
+ Main: <SwapFlow.Main inUsd={form.srcAmountUsd} outUsd={form.dstAmountUsd} />,
362
+ Success: <SwapFlow.Success title="Order created" />,
363
+ Failed: <SwapFlow.Failed error={parsedError?.message} />,
364
+ }}
365
+ />
366
+ );
367
+ }
368
+ ```
227
369
 
228
- // Inside a component rendered within SpotProvider:
229
- const { cancelOrder, isLoading, isSuccess, isError, error, txHash } = useCancelOrder(order);
370
+ Wrap `SwapFlow` in the DEX's modal shell and skin it with DEX colors, surfaces, typography, token logos, and loaders. Once `orderExecutionPanel.status` is set, hide the review details, confirm button, duplicate title, and secondary footer actions so the progress/success/failure content owns the modal.
230
371
 
231
- // Cancel the order
232
- await cancelOrder();
372
+ ### Execution State and Reset
233
373
 
234
- // Or use getCancelStatus for any order by ID:
235
- const { getCancelStatus } = useCancelOrder();
236
- const status = getCancelStatus(orderId); // { status, txHash?, error? } | undefined
374
+ `orderExecutionPanel` includes:
375
+
376
+ - `onSubmit`, `status`, `isLoading`, `isSuccess`, `isFailed`, and `confirmButtonLoading`;
377
+ - `step`, `stepIndex`, `totalSteps`, and `pendingSteps`;
378
+ - `parsedError`, `error`, `srcToken`, `dstToken`, `wrapTxHash`, and `approveTxHash`;
379
+ - `resetCurrentSwap()` and `resetState()`.
380
+
381
+ `spot-react` does not clear the DEX input. Do that only when a successful modal closes:
382
+
383
+ ```tsx
384
+ const { status, isSuccess, resetCurrentSwap, resetState } =
385
+ useSpot().orderExecutionPanel;
386
+
387
+ const onClose = useCallback(() => {
388
+ setIsModalOpen(false);
389
+
390
+ if (isSuccess) {
391
+ setInputAmount("");
392
+ setTimeout(resetState, 500);
393
+ } else if (status) {
394
+ setTimeout(resetCurrentSwap, 500);
395
+ }
396
+ }, [isSuccess, resetCurrentSwap, resetState, setInputAmount, status]);
237
397
  ```
238
398
 
399
+ Failed or rejected submissions should keep the user's input. The short delay lets the close animation finish before state resets.
400
+
239
401
  ## Callbacks
240
402
 
241
- Pass lifecycle callbacks to `SpotProvider` for wallet notifications:
403
+ Callbacks cover wallet notifications, analytics, field synchronization, and balance refresh:
242
404
 
243
405
  ```tsx
244
406
  const callbacks: Callbacks = {
245
- // Token wrapping
246
407
  onWrapRequest: () => {},
247
408
  onWrapSuccess: ({ txHash, explorerUrl, amount }) => {},
248
-
249
- // Token approval
250
409
  onApproveRequest: () => {},
251
410
  onApproveSuccess: ({ txHash, explorerUrl, token, amount }) => {},
252
-
253
- // Order lifecycle
254
411
  onSignOrderRequest: () => {},
255
412
  onSignOrderSuccess: (signature) => {},
256
413
  onSignOrderError: (error) => {},
257
414
  onOrderCreated: (order) => {},
258
415
  onOrderFilled: (order) => {},
259
-
260
- // Errors
416
+ onOrdersProgressUpdate: (orders) => {},
261
417
  onSubmitOrderFailed: ({ code, message }) => {},
262
418
  onSubmitOrderRejected: () => {},
263
-
264
- // Order cancellation
265
419
  onCancelOrderRequest: (order) => {},
266
420
  onCancelOrderSuccess: ({ order, txHash, explorerUrl }) => {},
267
421
  onCancelOrderFailed: (error) => {},
268
-
269
- // Progress (good place to refetch balances)
270
- onOrdersProgressUpdate: (orders) => {},
271
-
272
- // Clipboard
273
422
  onCopy: () => {},
274
-
275
- // State change callbacks
276
423
  onLimitPriceChange: (price) => {},
424
+ onLimitPricePercentChange: (percent) => {},
277
425
  onTriggerPriceChange: (price) => {},
426
+ onTriggerPricePercentChange: (percent) => {},
278
427
  onDurationChange: (duration) => {},
279
428
  onFillDelayChange: (fillDelay) => {},
280
429
  onChunksChange: (chunks) => {},
281
430
  };
282
431
  ```
283
432
 
284
- ## Translations / i18n
285
-
286
- The SDK returns string keys for user-facing messages. You should resolve these through your own i18n system.
433
+ Refetch balances in `onWrapSuccess`, `onOrderCreated`, `onOrderFilled`, `onOrdersProgressUpdate`, and `onCancelOrderSuccess`. Avoid an `onOrderCreated` toast unless the host DEX specifically wants one; the submit modal already shows creation success.
287
434
 
288
- ### Disclaimer keys
435
+ ## Cancellation and Order History
289
436
 
290
- `useSpot().disclaimerPanel` returns one of:
291
- - `"limitOrderDisclaimer"` — shown for limit and TWAP-limit orders
292
- - `"marketOrderDisclaimer"` — shown for market orders
293
- - `"triggerMarketPriceDisclaimer"` — shown for stop-loss at market price
437
+ Use the exported cancellation hook inside `SpotProvider`:
294
438
 
295
- ### Input error keys
439
+ ```tsx
440
+ import { OrderStatus, useCancelOrder } from "@orbs-network/spot-react";
296
441
 
297
- `useSpot().inputError` returns `{ type, args }` where `type` is a key like:
298
- - `"enterAmount"`, `"insufficientFunds"`, `"emptyLimitPrice"`, `"emptyTriggerPrice"`
299
- - `"maxChunksError"` (args: `{ maxChunks }`)
300
- - `"minChunksError"` (args: `{ minChunks }`)
301
- - `"minTradeSizeError"` (args: `{ minTradeSize }`)
302
- - `"minDurationError"` / `"maxDurationError"` (args: `{ duration }`)
303
- - `"minFillDelayError"` / `"maxFillDelayError"` (args: `{ fillDelay }`)
304
- - `"noLiquidity"`
442
+ function CancelButton({ order }) {
443
+ const { cancelOrder, disabled, isLoading, isSuccess, isError, error, txHash } =
444
+ useCancelOrder(order);
305
445
 
306
- ## Order Types
446
+ if (order.status !== OrderStatus.Open) return null;
307
447
 
308
- | Module | Description |
309
- | --- | --- |
310
- | `Module.TWAP` | Time-Weighted Average Price — splits order into multiple trades over time |
311
- | `Module.LIMIT` | Limit order — executes at specified price or better |
312
- | `Module.STOP_LOSS` | Stop-loss — triggers when price drops to threshold |
313
- | `Module.TAKE_PROFIT` | Take-profit — triggers when price rises to threshold |
448
+ return (
449
+ <button onClick={cancelOrder} disabled={disabled || isLoading}>
450
+ {isLoading ? "Cancelling..." : "Cancel"}
451
+ </button>
452
+ );
453
+ }
454
+ ```
314
455
 
315
- ### Panel visibility by module
456
+ For v2 orders, `disabled` remains true while RePermit configuration is loading or unavailable. Legacy v1 history and cancellation do not require that configuration. V2 history is added after configuration succeeds because its request needs the returned exchange adapter.
316
457
 
317
- | Panel | TWAP | LIMIT | STOP_LOSS | TAKE_PROFIT |
318
- | --- | --- | --- | --- | --- |
319
- | Trades amount | Yes | — | — | — |
320
- | Fill delay | Yes | — | — | — |
321
- | Duration | — | Yes | Yes | Yes |
322
- | Limit price | Yes | Yes | Yes | Yes |
323
- | Trigger price | — | — | Yes | Yes |
324
-
325
- ## Helper Hooks
458
+ Order history is available from `useSpot().orderHistoryPanel`:
326
459
 
327
460
  ```tsx
328
- import {
329
- useExplorerLink, // (txHash?: string) => explorer URL
330
- useNetwork, // () => network info (name, native, wToken, explorer)
331
- useAmountUi, // (amount: string, decimals: number) => formatted amount
332
- } from "@orbs-network/spot-react";
461
+ const { orders, isLoading, isRefetching, refetchOrders } =
462
+ useSpot().orderHistoryPanel;
463
+
464
+ // orders.all, orders.open, orders.completed, orders.cancelled, orders.expired
333
465
  ```
334
466
 
335
- ## Utility Functions
467
+ Use `useDerivedHistoryOrder(order, srcToken?, dstToken?)` for display fields. For large histories, use the virtualization library already present in the DEX for both the orders and fills lists. Store the selected order ID, then look up the current order from `orders.all`; do not store a stale copy of the order object.
336
468
 
337
- ```tsx
338
- import {
339
- getPartners, // () => all registered partners
340
- getPartnerChains, // (partner) => supported chain IDs
341
- getNetwork, // (chainId) => network config
342
- isNativeAddress, // (address) => boolean
343
- eqIgnoreCase, // (a, b) => case-insensitive address comparison
344
- getConfig, // (partner, chainId) => SpotConfig
345
- } from "@orbs-network/spot-react";
346
- ```
469
+ Keep history, details, and modal portals under `SpotProvider` context. Details should include execution summary, order info, fills, explorer/copy actions, and cancellation for open orders.
470
+
471
+ ## Translations
472
+
473
+ The SDK returns keys rather than final user-facing strings.
474
+
475
+ Disclaimer keys are:
347
476
 
348
- ## Overrides
477
+ - `limitOrderDisclaimer`
478
+ - `marketOrderDisclaimer`
479
+ - `triggerMarketPriceDisclaimer`
349
480
 
350
- For initial form state, pass `overrides` to `SpotProvider`:
481
+ Input errors have the shape `{ type, args }`. Resolve `type` through the DEX's i18n system and interpolate `args`, including values such as `maxChunks`, `minChunks`, `minTradeSize`, `duration`, and `fillDelay`. Current duration and fill-delay arguments are human-readable; custom or older integrations should convert raw milliseconds before display.
482
+
483
+ ## Helper and Advanced APIs
351
484
 
352
485
  ```tsx
353
- <SpotProvider
354
- overrides={{
355
- state: {
356
- isMarketOrder: false,
357
- chunks: 10,
358
- limitPrice: "1.5",
359
- triggerPrice: "1.2",
360
- fillDelay: { value: 5, unit: TimeUnit.Minutes },
361
- duration: { value: 1, unit: TimeUnit.Days },
362
- },
363
- }}
364
- // ...other props
365
- />
486
+ import {
487
+ useAmountUi, // (decimals?, rawAmount?) => formatted amount
488
+ useExplorerLink, // (txHash?) => explorer URL
489
+ useNetwork, // () => current network metadata
490
+ useRePermitData, // () => { data, error, isLoading, refetch }
491
+ useSignOrder, // low-level signing hook
492
+ useSubmitOrder, // low-level submission mutation
493
+ useSwapExecution, // low-level execution state
494
+ } from "@orbs-network/spot-react";
366
495
  ```
367
496
 
368
- ## Constants
497
+ Normal integrations should submit through `useSpot().orderExecutionPanel`. Use the low-level hooks only when deliberately replacing the built-in execution flow. `useRePermitData()` must be called under `SpotProvider`; most integrations should use the configuration state already exposed by `useSpot().submitOrderButton`.
498
+
499
+ Public utilities and constants include:
369
500
 
370
501
  ```tsx
371
502
  import {
372
- SPOT_VERSION, // SDK version identifier
373
- DISCLAIMER_URL, // Orbs disclaimer page
374
- ORBS_TWAP_FAQ_URL, // TWAP FAQ page
375
- ORBS_SLTP_FAQ_URL, // Stop-Loss/Take-Profit FAQ page
376
- ORBS_LOGO, // Orbs logo URL
377
- ORBS_WEBSITE_URL, // Orbs website
378
- networks, // All supported network configs
503
+ getMinChunkSizeUsd,
504
+ getNetwork,
505
+ getOrderExecutionRate,
506
+ getOrderFillDelayMillis,
507
+ getOrderLimitPriceRate,
508
+ getPartnerChains,
509
+ getPartners,
510
+ getTriggerPriceRate,
511
+ eqIgnoreCase,
512
+ isNativeAddress,
513
+ DISCLAIMER_URL,
514
+ ORBS_TWAP_FAQ_URL,
515
+ ORBS_SLTP_FAQ_URL,
516
+ ORBS_LOGO,
517
+ ORBS_WEBSITE_URL,
518
+ SPOT_VERSION,
519
+ networks,
379
520
  } from "@orbs-network/spot-react";
380
521
  ```
381
522
 
523
+ Always import from `@orbs-network/spot-react`. Do not import from `dist/*` or package-internal source paths, and verify public exports when upgrading.
524
+
525
+ ## Integration Checklist
526
+
527
+ - Use the DEX's existing tokens, inputs, selectors, components, styling, wallet controls, and routing.
528
+ - Keep chain switching out of the Spot token selector; use the DEX network control.
529
+ - Keep the form visible while disconnected; replace only the submit area with connect/switch controls.
530
+ - Persist Price Protection separately from swap slippage and hide the DEX slippage setting while Spot is active.
531
+ - Use `@orbs-network/swap-ui` for order creation/progress content.
532
+ - Convert raw amounts into the DEX's amount type before display.
533
+ - Use callbacks for balance refetch and reset the input only after success.
534
+ - Put Spot tabs beside the Swap tab using the DEX's navigation pattern.
535
+ - Split production integrations into focused `components`, `hooks`, `context`, and `utils` files.
536
+ - Do not add another error boundary around Spot; `SpotProvider` already includes one.
537
+
382
538
  ## License
383
539
 
384
540
  MIT