@orbs-network/spot-ui 1.0.28 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # Spot SDK
2
+
3
+ `@orbs-network/spot-ui` is the framework-agnostic Spot SDK. Use it from Vue,
4
+ Angular, Svelte, React, vanilla JavaScript, or a server-side TypeScript
5
+ application. It has no React dependency.
6
+
7
+ ## Complete form calculation
8
+
9
+ Use `calculateOrderForm` as the primary calculation API. Pass the DEX-owned
10
+ form state and market data; the SDK returns one authoritative model for both
11
+ display and order execution.
12
+
13
+ ```ts
14
+ import {
15
+ calculateOrderForm,
16
+ Module,
17
+ toAmountWei,
18
+ } from "@orbs-network/spot-ui";
19
+
20
+ const form = calculateOrderForm({
21
+ module: Module.TWAP,
22
+ isMarketOrder: true,
23
+
24
+ // Raw input-token amount.
25
+ inputAmountWei: toAmountWei(typedInputAmount, inputToken.decimals),
26
+ inputTokenDecimals: inputToken.decimals,
27
+ outputTokenDecimals: outputToken.decimals,
28
+
29
+ // Raw output-token amount quoted for the complete input amount.
30
+ quotedOutputAmount,
31
+ inputUsdPrice,
32
+ outputUsdPrice,
33
+ minTradeSizeUsd,
34
+
35
+ // Optional user overrides. Omit them to use SDK defaults.
36
+ trades,
37
+ fillDelay,
38
+ duration,
39
+ limitPrice,
40
+ limitPricePercent,
41
+ triggerPrice,
42
+ triggerPricePercent,
43
+ isInverted,
44
+
45
+ priceProtection: 3, // 3%
46
+ displayFeePercent,
47
+ inputBalance, // raw input-token units
48
+ });
49
+ ```
50
+
51
+ The result includes:
52
+
53
+ ```ts
54
+ form.inputAmount; // raw, UI, and USD input amounts
55
+ form.outputAmount; // raw, UI, and USD output amounts
56
+ form.isInverted; // current price-display direction
57
+ form.trades; // trade count and structured per-trade input/output amounts
58
+ form.schedule; // resolved fill delay, duration, milliseconds, and errors
59
+ form.triggerPrice; // canonical raw value, display values, defaults, and validation
60
+ form.limitPrice; // canonical raw value, display values, defaults, and validation
61
+ form.minOutputAmountTotal; // raw, UI, and USD minimum total output
62
+ form.tradePrice; // raw, UI, and USD execution price
63
+ form.fees; // raw, UI, USD, and percentage fee values
64
+ form.values; // raw execution values used to build the order
65
+ form.errors; // structured errors, ordered list, and primary error
66
+ form.isReady;
67
+ form.canSubmit;
68
+ ```
69
+
70
+ Display amounts use `{ raw, ui, usd }` objects and
71
+ trade-direction terminology. For `limitPrice` and `triggerPrice`, `amount`
72
+ is now named `raw` and remains the canonical protocol rate, while
73
+ `display.raw`, `display.ui`, and `display.usd` follow the current `isInverted`
74
+ display direction. `form.values` intentionally contains no `UI` or `Usd`
75
+ fields. Integration-facing calculation and client APIs use input/output names.
76
+ Existing source/destination names remain only on low-level protocol helpers and
77
+ protocol response models such as `Order`.
78
+
79
+ This calculation is synchronous and does not fetch configuration. Optional
80
+ fields can be passed while the user edits the form; the result always contains
81
+ the currently derivable values. `inputAmountWei` is the single input amount
82
+ input; the SDK derives its UI and USD representations from the token decimals
83
+ and USD price. Integrations that already have a per-input-token raw rate can
84
+ pass `marketPrice` instead of `quotedOutputAmount`.
85
+
86
+ `marketPrice` and `quotedOutputAmount` use different units:
87
+
88
+ - `marketPrice` is the raw output-token amount for exactly one whole input
89
+ token (for example, a 2 USDC rate with 6 output decimals is `"2000000"`);
90
+ - `quotedOutputAmount` is the raw output-token amount quoted for the complete
91
+ `inputAmountWei`.
92
+
93
+ Pass one or the other. When `quotedOutputAmount` is supplied, the SDK derives
94
+ the per-token rate using `inputAmountWei` and `inputTokenDecimals`.
95
+
96
+ `calculateOrderForm` is time-independent. Recalculate only when its form or
97
+ market inputs change. `prepareOrder` stamps the current start and deadline from
98
+ the calculated duration immediately before signing; those exact timestamps are
99
+ returned on `preparedOrder.values`.
100
+
101
+ `calculateOrderForm` is the only public order-calculation entry point. Its
102
+ smaller calculators are internal implementation details, so every integration
103
+ uses the same defaults, validation, and derived-value rules.
104
+
105
+ `minTradeSizeUsd` is a positive USD threshold owned by the integrating DEX.
106
+ Use the minimum approved for that partner/product; the SDK deliberately does
107
+ not guess protocol policy. `priceProtection` is a percentage, so `3` means 3%
108
+ (300 basis points), not 3 bps. `displayFeePercent` is also a percentage, but it
109
+ only calculates `form.fees` for display. It does not collect or subtract a fee;
110
+ collection must be configured by the partner/backend.
111
+
112
+ ## Client and order submission
113
+
114
+ `createClient` loads RePermit configuration and returns a new initialized
115
+ client. It does not retain a module-level cache; the hosting application owns
116
+ client reuse, request deduplication, and refresh policy. Initialization rejects
117
+ chain mismatches and malformed or zero RePermit and exchange-adapter addresses
118
+ before exposing approval or cancellation values.
119
+
120
+ ### Migrating from 1.x
121
+
122
+ Version 2 uses `calculateOrderForm` as the single calculation entry point and
123
+ an initialized `createClient` for order preparation, signing, submission,
124
+ cancellation requests, and configured history. Legacy low-level order-building
125
+ and submission exports were removed so integrations cannot bypass the shared
126
+ validated form and client configuration.
127
+
128
+ ```ts
129
+ import {
130
+ calculateOrderForm,
131
+ createClient,
132
+ ensureWrappedToken,
133
+ getExplorerUrl,
134
+ isNativeAddress,
135
+ isTxRejected,
136
+ Module,
137
+ Partners,
138
+ toAmountWei,
139
+ } from "@orbs-network/spot-ui";
140
+
141
+ const client = await createClient(Partners.Quick, 137);
142
+
143
+ const form = calculateOrderForm({
144
+ module: Module.TWAP,
145
+ isMarketOrder: true,
146
+ inputAmountWei: toAmountWei(typedInputAmount, inputToken.decimals),
147
+ inputTokenDecimals: inputToken.decimals,
148
+ outputTokenDecimals: outputToken.decimals,
149
+ quotedOutputAmount,
150
+ inputUsdPrice,
151
+ outputUsdPrice,
152
+ minTradeSizeUsd,
153
+ trades,
154
+ fillDelay,
155
+ duration,
156
+ limitPrice,
157
+ limitPricePercent,
158
+ triggerPrice,
159
+ triggerPricePercent,
160
+ isInverted,
161
+ priceProtection: 3,
162
+ displayFeePercent,
163
+ inputBalance,
164
+ });
165
+
166
+ const amount = form.inputAmount.raw;
167
+ const approvalToken = ensureWrappedToken(inputToken, client.chainId);
168
+ const approvalRequest = {
169
+ tokenAddress: approvalToken.address,
170
+ amount,
171
+ spenderAddress: client.spenderAddress,
172
+ };
173
+ const { tokenAddress, spenderAddress } = approvalRequest;
174
+ const hasAllowance = async () =>
175
+ BigInt(await wallet.getAllowance({ tokenAddress, spenderAddress })) >=
176
+ BigInt(amount);
177
+
178
+ try {
179
+ const approvalRequired = !(await hasAllowance());
180
+
181
+ // The signed order spends wrapped native tokens, so wrap before approval.
182
+ if (isNativeAddress(inputToken.address)) {
183
+ await wallet.wrapNativeToken(amount);
184
+ }
185
+
186
+ if (approvalRequired) {
187
+ await wallet.approveToken(approvalRequest);
188
+
189
+ // Allow RPC allowance state to catch up after the confirmed approval.
190
+ for (let attempt = 0; attempt < 3 && !(await hasAllowance()); attempt++) {
191
+ await new Promise((resolve) => setTimeout(resolve, 3_000));
192
+ }
193
+ if (!(await hasAllowance())) throw new Error("Approval was not observed");
194
+ }
195
+
196
+ const preparedOrder = client.prepareOrder({
197
+ form,
198
+ inputTokenAddress: inputToken.address,
199
+ outputTokenAddress: outputToken.address,
200
+ swapperAddress: account,
201
+ });
202
+ const signature = await client.signOrder(
203
+ preparedOrder,
204
+ ({ signerAddress, typedData }) =>
205
+ wallet.signTypedData(typedData, signerAddress),
206
+ );
207
+ const order = await client.submitOrder(preparedOrder, signature);
208
+ console.info("Order submitted", order, getExplorerUrl(order.txHash, 137));
209
+ } catch (error) {
210
+ if (isTxRejected(error)) console.info("The wallet request was rejected");
211
+ else throw error;
212
+ }
213
+ ```
214
+
215
+ `signOrder` only invokes the supplied wallet signer and returns its signature.
216
+ It never submits the order. `submitOrder` is the separate network operation.
217
+ The complete sequence is allowance check, native wrapping when required,
218
+ approval when required, allowance verification, signing, and submission.
219
+
220
+ `prepareOrder` does not recalculate form amounts, prices, trades, or schedules,
221
+ and it rejects a form whose `canSubmit` value is `false`.
222
+ It stamps the current start and deadline from the calculated duration, then
223
+ converts the supplied form into RePermit, signing, and approval data. The
224
+ returned `PreparedOrder` contains:
225
+
226
+ - `form`, the complete calculated form snapshot shown to the user;
227
+ - `values`, `form.values` plus the exact preparation-time timestamps used by the
228
+ signed order;
229
+ - `order`, the resulting protocol order;
230
+ - `signingRequest`, containing a framework-neutral `signerAddress` and
231
+ `typedData` EIP-712 payload;
232
+ - `approvalRequest`, ready for the wallet approval adapter.
233
+
234
+ Each `prepareOrder` call assigns a fresh monotonic nonce within that client
235
+ instance, so two orders prepared by the same instance cannot reuse a nonce. A
236
+ new client instance starts again from the current wall-clock value. The call
237
+ also assigns fresh `currentTimeMillis` and `deadlineMillis` values for that
238
+ submission attempt. Call it after wrapping and approval, immediately before
239
+ signing, so those time-dependent values remain fresh.
240
+
241
+ The signing request does not depend on Viem, Wagmi, Ethers, or another wallet
242
+ library. Viem adapters can spread `typedData` and map `signerAddress` to
243
+ `account`; Ethers adapters can pass `typedData.domain`, `typedData.types`, and
244
+ `typedData.message` to the signer.
245
+
246
+ `minTradeSizeUsd` is required calculation input owned by the integrating
247
+ application. The client does not read or infer it from partner configuration.
248
+
249
+ The complete form calculation remains a package-level function because it does
250
+ not depend on partner or chain configuration. The client exposes only
251
+ configured operations:
252
+
253
+ ```ts
254
+ calculateOrderForm(formParams);
255
+ client.getCancelOrderRequest(order);
256
+ client.getAccountOrders({ account });
257
+ ```
258
+
259
+ Omitting `page` fetches every available history page. To fetch one page, pass
260
+ a zero-based `page` and an optional positive `limit`. Use `order.historyKey`
261
+ as the stable list/cache identity: legacy v1 numeric IDs can repeat across TWAP
262
+ contract deployments, while `order.id` remains the protocol order ID used for
263
+ display and cancellation.
264
+
265
+ The normalized history model keeps protocol response names (`src`/`dst` and
266
+ fill `in`/`out`) because it combines v1 and v2 payloads. Form and calculation
267
+ inputs use `input`/`output`. `OrderType` maps the selected module and execution
268
+ mode into `LIMIT`, `TWAP_LIMIT`, `TWAP_MARKET`, `STOP_LOSS_LIMIT`,
269
+ `STOP_LOSS_MARKET`, `TAKE_PROFIT_LIMIT`, or `TAKE_PROFIT_MARKET`.
270
+ `OrderFilter` provides `ALL`, `OPEN`, `COMPLETED`, `CANCELLED`, and `EXPIRED`
271
+ history filter values.
272
+
273
+ There is no authoritative single-order endpoint in the current service API.
274
+ To track an order, call `client.getAccountOrders({ account, page, limit })` on
275
+ the host's polling schedule and find it by `historyKey`. Supplying a page and
276
+ limit avoids fetching every history page when only a recent status window is
277
+ needed.
278
+
279
+ Use `client.spenderAddress` for allowance reads and approvals. Normalize a
280
+ native input with `ensureWrappedToken`, and approve `form.inputAmount.raw`.
281
+ The `approvalRequest` returned by `prepareOrder` records the same normalized
282
+ token, spender, and exact amount used by the signed order.
283
+
284
+ Native token addresses are normalized to the chain's wrapped token for order
285
+ input and approval requests. Protocol configuration fetching, RePermit order
286
+ construction, submission, cancellation request construction, and configured
287
+ history access are intentionally exposed only through `SpotClient`. This keeps
288
+ partner- and chain-derived values on one authoritative path.
289
+
290
+ Framework-neutral helpers `ensureWrappedToken`, `shouldWrapOnly`,
291
+ `shouldUnwrapOnly`, `isTxRejected`, and `getExplorerUrl` are also exported from
292
+ `spot-ui`; Vue, Angular, Svelte, and server integrations do not need to copy
293
+ React-specific utility code.
294
+
295
+ Every `createClient` call performs a new configuration request. Cache the
296
+ returned promise or client in the host application's normal data layer when it
297
+ should be reused. `spot-react` keeps one provider-scoped client resource keyed
298
+ by partner and chain; it does not require React Query. Vue, Angular, Svelte,
299
+ vanilla JavaScript, and server applications should apply their own lifecycle
300
+ and refresh policy.
301
+
302
+ ```ts
303
+ // Illustrative host-owned cache; use the host framework's data layer where possible.
304
+ const clientPromise = createClient(Partners.Quick, 137);
305
+ const client = await clientPromise;
306
+ ```