@spicenet-io/spiceflow-ui 4.15.2 → 4.15.3

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 DELETED
@@ -1,520 +0,0 @@
1
- # SpiceFlow UI SDK
2
-
3
- A React component library for building DeFi swap interfaces with multi-chain support.
4
-
5
- ## Version and migration
6
-
7
- Release `4.11.0` includes quote prewarming, scoped fee-hint reuse, concurrent fee
8
- checks, configurable fee-estimate tolerance, and faster execution-status polling.
9
- It uses `@spicenet-io/spiceflow-core@0.7.3`. Source spend caps, destination minimums
10
- and required destination receipts remain enforced. See the fee-estimate tolerance
11
- section below for the default and configuration.
12
-
13
- Check the actual registry tag and client pin before adopting it. See
14
- [CONSISTENCY-MIGRATION.md](CONSISTENCY-MIGRATION.md) for the earlier inventory/action
15
- migration. Publishing the SDK does not upgrade consuming apps automatically.
16
-
17
- ## Installation and provider setup
18
-
19
- Select an approved exact version, preserving the app's dependency-age and security policies:
20
-
21
- ```bash
22
- bun add --exact @spicenet-io/spiceflow-ui@4.11.0
23
- ```
24
-
25
- Reuse compatible host-owned Privy, wagmi and QueryClient providers. Read the exact
26
- package's peer dependencies; do not replace the wallet stack as part of a version bump.
27
-
28
- The SDK adds its stylesheet (spinner keyframes, focus ring, reduced-motion rule) to the
29
- page when it loads, so no CSS import is needed. `@spicenet-io/spiceflow-ui/styles.css`
30
- is still exported for a host whose Content Security Policy forbids inline styles;
31
- importing it as well is harmless.
32
-
33
- ```tsx
34
- import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui";
35
-
36
- // Inside the host's existing WagmiProvider → QueryClientProvider → PrivyProvider stack.
37
- <SpiceFlowProvider
38
- provider="privy"
39
- network="mainnet"
40
- nativeChainId={8453}
41
- rpcOverrides={clientRpcUrls}
42
- >
43
- {children}
44
- </SpiceFlowProvider>;
45
- ```
46
-
47
- Configure Privy credentials/policy on the host's `PrivyProvider`. Supply client RPCs
48
- through `rpcOverrides` for the chains you exercise, and keep the SDK/wallet chain sets
49
- aligned. An entry in `rpcOverrides` always wins; a chain without one uses the chain's
50
- built-in endpoint, so an integration that passes none keeps working. Wallet-library
51
- transports alone do not configure SDK reads. Rollup price lookups read only a
52
- host-configured RPC. Keep private credentials out of browser bundles.
53
-
54
- ## Integration ownership
55
-
56
- - Use stock components when they fit the approved UI; use `useSpiceSupply` for custom
57
- action forms. `useSpiceLock` and legacy `useSpiceAction` delegate to that controller.
58
- - In this source (not yet published), `useSpiceSupply` defaults `enabled` to `true`.
59
- Omit it for a mounted action form, or pass `enabled: isOpen` when a hidden form stays
60
- mounted. Use `canExecute` for submit-button readiness; fee calculation does not
61
- require toggling `enabled`. Published 4.10.0 still requires an explicit value.
62
- - Declare the destination token by chain and address:
63
- `destinationToken={{ chainId: 8453, address: USDC }}`. The SDK takes the symbol and
64
- decimals from the chain's token registry, then from the token contract, and holds the
65
- action until they are known. Decimals that cannot be read block the action; a symbol
66
- that cannot be read falls back to the shortened address. Any field you pass wins, so a
67
- full `{ address, symbol, decimals, chainId }` object behaves exactly as before.
68
- - Supply destination metadata, protocol `buildActionCalls` or destination-only
69
- `lockBatches`, beneficiary semantics, and awaited application updates. The SDK owns
70
- inventory, routing, Max, fees, accepted plans, funding, execution and recovery.
71
- - For the account, use `useSpiceAccount()` instead of reading `SpiceFlowProviderContext`:
72
- `mode` (`"embedded"` or `"external"`), `setMode`, `ownerAddress` for the current mode,
73
- both wallet addresses and the whitelist state. A provider with a fixed `mode` prop
74
- ignores `setMode`.
75
- - For custom holdings, use `useSpiceInventory` and `assetPositionId`. Keep raw balance,
76
- default visibility and spend eligibility separate. Preserve rows during refresh;
77
- stale/failed balances cannot authorize spending, and confirmed zero wins.
78
- - In this unreleased source, eligible External-mode actions with a matching wallet token
79
- and destination chain execute directly from the external signer, without embedded funding.
80
- Other assets/chains still use embedded funding and the shared routed executor.
81
- A controlled ERC20 action can opt into `actionExecution: { wallet: "external", recoveryKey: "my-action" }`
82
- when its contract requires the external caller. The SDK routes funds to that wallet,
83
- verifies delivery, then submits the captured host calls. This also permits a matching
84
- external source to execute directly while the app remains in Embedded mode.
85
- `onPayExecute` and `onLockExecute` remain blocked. Verify contract caller/beneficiary and NFT receiver behavior.
86
- Published `.16` does not contain this restoration.
87
- - Supply `input` adds fees to principal; `input-inclusive` uses a total budget; Pay
88
- `output` fixes the destination amount. Use controller readiness/Max/status instead of
89
- constructing an executor context or duplicating fee arithmetic in the host.
90
- - Use controller recovery for uncertain funding and `retryFinalization()` for failed
91
- application updates after settlement. Preserve original host terms across reload.
92
- - Deposit and rollup Withdraw retain distinct execution semantics while sharing
93
- inventory/policy. Account wallet withdrawal uses the fee-only Supply path.
94
-
95
- Inventory isolation does not establish support for simultaneous differently configured
96
- providers: underlying quote/execution transports still have global state.
97
-
98
- ## Liquidity venues
99
-
100
- When a flow has to swap on the destination chain, the SDK quotes every liquidity
101
- venue that covers that chain and takes the best fill: the most output on a
102
- supply, the least input on a pay. Routing is decided here, on your RPC. The
103
- solver only executes the calls the winning quote produced.
104
-
105
- Leave `liquidityVenues` off and you get the built-in venues. Declare it once on
106
- the provider to add your own, and every component picks them up.
107
-
108
- ```tsx
109
- <SpiceFlowProvider
110
- network="mainnet"
111
- nativeChainId={4663}
112
- rpcOverrides={{ 4663: ROBINHOOD_RPC }}
113
- liquidityVenues={{
114
- venues: [
115
- {
116
- protocol: "uniswap-v3",
117
- chainId: 4663,
118
- swapRouter: "0x1e406484F1F204b23cE84B9901C0171a738fd406",
119
- quoterV2: "0x3E290e5E01818002A0b672148BdC7514D861C7B3",
120
- hubCurrencies: ["0x0bd7d308f8e1639fab988df18a8011f41eacad73"],
121
- },
122
- ],
123
- }}
124
- >
125
- ```
126
-
127
- Your venues race the built-ins on price; they do not override them. Set
128
- `includeDefaults: false` to drop the built-ins and route only through what you
129
- declared. On a chain with no venue at all, quoting fails with that chain named
130
- rather than falling back to something you did not configure.
131
-
132
- Every entry names one chain and one protocol family, and serves both swap
133
- directions. Supply the **deployment's** addresses, not a pool address: a
134
- v3-style pool calls back into `msg.sender`, so swaps go through the router and
135
- quotes through the quoter. The SDK finds the pools from the fee tiers (or tick
136
- spacings) and hub currencies.
137
-
138
- | `protocol` | Required | Optional |
139
- | ---------------------- | ------------------------------------------------- | -------------------------------------------------------- |
140
- | `uniswap-v3` | `chainId`, `swapRouter`, `quoterV2` | `name`, `feeTiers`, `hubCurrencies`, `routerHasDeadline` |
141
- | `uniswap-v4` | `chainId`, `quoter`, `universalRouter`, `permit2` | `name`, `hubCurrencies` |
142
- | `aerodrome-slipstream` | `chainId`, `swapRouter`, `quoterV2` | `name`, `tickSpacings`, `hubCurrencies` |
143
-
144
- `name` is the label shown to the user as "via <name>" once a swap is quoted, so
145
- give it the name people know the venue by. It defaults to the protocol, which
146
- means a swap always names something real rather than a family it does not belong
147
- to.
148
-
149
- `hubCurrencies` are the intermediate tokens tried for two-hop routes, usually
150
- wrapped native and the chain's main stablecoin. Set `routerHasDeadline` when the
151
- deployment forked the original Uniswap `SwapRouter`, whose exact-output structs
152
- carry a deadline; SwapRouter02 forks do not.
153
-
154
- Most DEXs are forks of one of these families, so adding one is addresses only.
155
- A venue from a different family (a Uniswap v2 pair, Curve, Balancer) needs an
156
- adapter in the SDK — send us the chain, router, quoter and one funded pool and
157
- it becomes a `protocol` everyone can name.
158
-
159
- ## Components
160
-
161
- - **SwapWidget** - Main swap interface component
162
- - **AssetSelector** - Token selection component
163
- - **SpiceDeposit** - Deposit modal component
164
- - **StatusDisplay** - Transaction status display
165
- - **ProviderLogin** - Wallet connection component
166
- - **DebugWidget** - Development debugging component
167
- - **Button**, **Input**, **Modal** - UI primitives
168
-
169
- ## Development
170
-
171
- ### Rollup balance pricing
172
-
173
- Rollup USD values use the host-configured RPC provider's pricing capability when the balance API has no valid value. The built-in account panel and withdrawal modal enable this while open; custom `useSpiceAssets` consumers can opt in with `priceEnabled`. Quantities arrive independently. Lookups follow receipt mappings, prefer `nativeChainId`, and fall back to other supported chains with a configured Alchemy endpoint. Explicit account `tokenPrices` overrides still take precedence over provider quotes.
174
-
175
- Wallet discovery and rollup balances share a one-minute quote cache. Pricing polls only while the view is open and the page is visible. A failed refresh can retain a marked stale quote for up to five minutes; an unavailable quote displays `—`. No extra key, internal RPC, or symbol-based ERC-20 price fallback is used.
176
-
177
- ### Host tokens
178
-
179
- A token the SDK does not know (a protocol's own token, a receipt token) is declared
180
- once on the provider. Every surface that lists balances then reads it: Supply, Pay,
181
- Lock, the account panel and Withdraw.
182
-
183
- ```tsx
184
- <SpiceFlowProvider
185
- additionalSourceTokens={[{ address: TOKEN, chainId: 8453, symbol: "TOKEN", decimals: 18 }]}
186
- >
187
- ```
188
-
189
- A component's or hook's own `additionalSourceTokens` still works and is added to the
190
- provider's list.
191
-
192
- ### Host token prices
193
-
194
- A host token usually has no price the SDK can find, so the account panel lists it
195
- without a USD value. `AccountDisplay.tokenPrices` takes the host's own price for it,
196
- keyed by `chainId:address`. The value counts toward the account total and the row
197
- sorts with the rest.
198
-
199
- ```tsx
200
- <AccountDisplay tokenPrices={{ [`8453:${TOKEN}`]: tokenUsdPrice }} />
201
- ```
202
-
203
- The SDK's own price wins whenever it has one. A symbol key prices rollup balances
204
- only: any wallet token can carry any symbol, so a wallet balance is matched by address
205
- alone. The price is for display and never changes which balance an action spends.
206
-
207
- ### Host-supplied token logos
208
-
209
- `AccountDisplay.tokenLogos` maps a token symbol to a logo URL, the same key
210
- shape `tokenPrices` uses. A discovered embedded-wallet balance falls back to
211
- it when the RPC provider returns no logo, which is what happens for a token
212
- that is not in the chain's configured token list. Provider metadata still
213
- wins when it has a logo.
214
-
215
- ```tsx
216
- <AccountDisplay tokenLogos={{ TOKEN: tokenLogoUrl }} />
217
- ```
218
-
219
- For a host-rendered asset row, this source also exports the SDK's existing logo
220
- resolver from the package root:
221
-
222
- ```tsx
223
- const logo = useTokenLogo(asset.logoURI, asset.chainId, asset.address);
224
- ```
225
-
226
- Import `useTokenLogo` from `@spicenet-io/spiceflow-ui`. It returns the supplied logo
227
- immediately, otherwise resolves the shared chain/address token list asynchronously.
228
- Until a logo is available, it returns `undefined`; keep the balance and selection
229
- usable and render the host's usual fallback. This source export requires an SDK
230
- release before a consuming app can adopt it.
231
-
232
- ### Account with Deposit and Withdraw
233
-
234
- `SpiceAccount` renders `AccountDisplay` together with `SpiceDeposit` and
235
- `SpiceWithdraw`, and owns their open state. Withdraw on an idle rollup balance opens
236
- the modal with that balance selected. It takes every `AccountDisplay` prop except the
237
- two click handlers; `depositProps` and `withdrawProps` pass through to the modals.
238
- `styles` and `onDepositSuccess` are shared with the modals unless those props set
239
- their own.
240
-
241
- ```tsx
242
- <SpiceAccount appName="MyApp" onDepositSuccess={refresh} />
243
- ```
244
-
245
- Use `AccountDisplay` directly to hide one of the buttons or to open your own flow
246
- from them.
247
-
248
- ### Withdraw from an account balance
249
-
250
- `AccountDisplay.onWithdrawClick` receives the rollup receipt-token ID when a
251
- user clicks Withdraw on an idle rollup balance. The main Withdraw button calls
252
- it without an ID. Pass the ID to `SpiceWithdraw.initialSourceAssetId` to open
253
- the existing modal with that asset selected; the user chooses the destination
254
- network inside the modal. Existing callbacks that take no arguments continue
255
- to work.
256
-
257
- ```tsx
258
- const [withdrawOpen, setWithdrawOpen] = useState(false);
259
- const [withdrawAssetId, setWithdrawAssetId] = useState<string>();
260
-
261
- <AccountDisplay
262
- onWithdrawClick={(receiptTokenId) => {
263
- setWithdrawAssetId(receiptTokenId);
264
- setWithdrawOpen(true);
265
- }}
266
- />
267
- <SpiceWithdraw
268
- isOpen={withdrawOpen}
269
- onClose={() => setWithdrawOpen(false)}
270
- initialSourceAssetId={withdrawAssetId}
271
- />
272
- ```
273
-
274
- `buildSpicenetBatch` defaults to the SDK's `buildSpicenetWithdrawalBatch`. An
275
- integration that already passes a builder keeps it as an override, and its
276
- callbacks are unchanged.
277
- `initialSourceAssetId` selects a current balance by ID; it does not supply a
278
- cached balance or change the receipt's supported withdrawal routes.
279
-
280
- ### Funding sources
281
-
282
- Supply, Pay and Lock list every balance that can fund the action: the embedded wallet
283
- and rollup balances, and the connected external wallet, in either wallet mode. To keep a
284
- mode to its own wallet, pass `includeExternalWalletAssets={false}` (Embedded mode) or
285
- `includeCrossChainEmbeddedAssets={false}` (External mode). A user who holds the same
286
- token in both wallets sees one row per wallet; a host that preselects a source should
287
- match on `assetPositionId`, not on symbol or address alone.
288
-
289
- ### Routed external callers
290
-
291
- The controlled hook supports positive ERC20 input, input-inclusive and fixed-output
292
- actions with an explicit external caller. Configure `rpcOverrides` for the destination
293
- and any wallet-source chain. Quotes bind the source spend cap, destination delivery,
294
- external caller and host calls before funding. The SDK checks the destination receipt,
295
- ERC20 transfer amount and caller balance before requesting the host transactions.
296
- Only the final external transaction hash reaches awaited application finalization.
297
-
298
- Below one destination transaction's native gas cost, the accepted route buys gas for
299
- the pending external calls plus three later transactions. At or above that threshold
300
- it adds no reserve; direct execution still pays its own native gas. Native destinations,
301
- zero-principal, proceeds-funded and explicit source-escrow caller actions remain
302
- unsupported capability gaps. This does not add a stock-component prop or a durable
303
- execution journal: after signing fails, delivered tokens remain ordinary wallet assets
304
- and retrying the original route can fund again. Use `retryFinalization()` to retry only
305
- a failed application callback after the external action has confirmed.
306
-
307
- ### Action Fee Readiness
308
-
309
- - Routed Supply, Pay and Lock actions must surface fee estimation before enabling submission.
310
- - Eligible direct external actions still require an accepted plan but charge no Spicenet fee. Network gas remains payable in the external wallet; native Max reserves gas.
311
- - Deposit gas and rollup withdrawal semantics remain distinct from action intent fees. Preserve their existing quote/readiness requirements.
312
- - Account wallet withdrawal is a fee-only Supply action. Do not bypass its fee/readiness gates based on the label "withdraw".
313
- - Custom actions use the shared controller's `canExecute`; an available balance or old quote alone is insufficient.
314
- - `onSubmit` on `SpiceSupply`, `SpiceLock`, `SpiceDeposit` and the shared controller fires once when the user submits and the SDK accepts it, before any wallet prompt. Use it for "transaction in progress" feedback instead of listening for clicks on SDK buttons.
315
- - `max()` on the shared controller (`useSpiceSupply`, `useSpiceLock`) resolves the fee-aware Max for the selected source, or `null` when there is none or the action fixes its output. The host sets the result as the amount.
316
-
317
- ### Fee funding for funded actions
318
-
319
- Set `feeFunding: ["input", "proceeds", "balance"]` on `useSpiceSupply`,
320
- `SpiceSupply`, `useSpiceAction`, `useSpiceLock` or `SpiceLock` to use the shared
321
- hierarchy for wallet-funded actions on the destination chain. This applies to
322
- arbitrary protocol calls: swaps, deposits, staking, lending and repayments.
323
- Direct payment precedes conversion **within each tier**. Existing defaults remain
324
- unchanged; `feeFunding: []` retains the existing selected-source path.
325
-
326
- - `amountDirection: "input-inclusive"` offers the selected budget as input.
327
- Fees reduce that input and the SDK requotes the conversion before building
328
- protocol calls. The builder must honour the amount passed to it.
329
- - `"input"` preserves the selected principal; `"output"` preserves the exact
330
- payment or repayment. Neither offers required principal to the input tier.
331
- - `actionProceeds: { token, resolve }` declares an amount remaining in the
332
- executor **after all protocol calls**. Conversion output that is deposited,
333
- staked, used for repayment or sent away is not available for fees. The SDK
334
- resolves proceeds only if it reaches that tier and rechecks them before signing.
335
- The object declaration alone does not opt a funded action in: it requires a
336
- nonempty `feeFunding` order, preserving existing callers' selected-source planning.
337
- - Balance fallback reserves the selected principal. Only the remaining amount
338
- of that exact position can pay fees. `fees.sourceSpendCap` includes fee spending
339
- from the same position, and `fees.fundingSource` reports the chosen source.
340
-
341
- For an input-inclusive swap whose output stays in the executor,
342
- `actionProceeds: "output"` enables the same hierarchy and declares the output as
343
- available proceeds. If proceeds pay fees, `fees.destinationAmount` and
344
- `quote.estimatedOutput` show the net minimum and expected output respectively.
345
- Do not use this shortcut for calls that consume or send away the conversion output.
346
-
347
- Local external-wallet funding still transfers the principal to the executor
348
- first. Direct external execution keeps its native-gas path. External native Max
349
- reserves transfer gas, and fixed-principal Max reserves execution fees.
350
- Cross-chain and rollup **principal** routes retain their existing funding planner;
351
- this extension does not let destination proceeds pay earlier source or bridge
352
- costs, or add balance fallback to those routes. Custom fee orders on those routes fail
353
- explicitly instead of silently charging a different tier. Rollup balances can still fund
354
- fees for a local action through the existing fee-funding withdrawal leg. Standalone
355
- `SpiceDeposit` and rollup `SpiceWithdraw` retain their distinct transfer semantics;
356
- protocol deposits/withdrawals through the action controller use the contract above.
357
-
358
- All paths retain fee readiness and accepted-plan validation. This capability
359
- requires an SDK release and explicit integration opt-in; it does not upgrade
360
- published packages or deployed apps.
361
-
362
- ### Supply fee-estimate tolerance
363
-
364
- In `4.11.0`, `SpiceFlowProvider` defaults `feeToleranceBps` to
365
- `4000` (40%). Clients can override it, for example:
366
-
367
- ```tsx
368
- <SpiceFlowProvider {...config} feeToleranceBps={100}>
369
- {children}
370
- </SpiceFlowProvider>
371
- ```
372
-
373
- `100` is 1%; `0` disables the drift allowance. Existing conservative fixed-fee
374
- retention still applies when estimates fall. Values must be integers from `0` to
375
- `10000`. The allowance is relative to each accepted fee component: a fee of 100
376
- allows a fresh estimate up to 140 at the default. A zero fee cannot become positive.
377
-
378
- This setting covers fee convergence and pre-sign validation for user-transfer
379
- Supply plans, including stock/controlled Supply and input-mode Lock consumers.
380
- Within tolerance, the SDK keeps the quoted fee amounts and transaction calls;
381
- it does not add a 40% fee buffer, increase the source spend cap, or relax swap
382
- slippage/minimum outputs. Changing the setting invalidates prepared plans.
383
- Backend acceptance and funding checks still apply. Exact-output Pay, fee-only
384
- funding, deposits and backend-transfer convergence retain their separate rules.
385
-
386
- The open Supply controller retains fee construction hints for at most 30 seconds
387
- across amount edits in the same wallet, route and configuration scope. These hints
388
- do not enable submission: the SDK rebuilds the current calls, then checks source
389
- gas and destination fees concurrently. User-transfer source gas uses the same
390
- configured fee tolerance; accepted amounts and source caps stay unchanged. A hint
391
- that no longer fits falls back to full preparation. Closing the form clears hints.
392
- Servers advertising fee validation price the already constructed destination fee
393
- calls; older servers retain the estimate path. Cold preparation still sizes fees
394
- from scratch. Input route quotes debounce for 100 ms; routed fee preparation adds
395
- no second debounce. These optimizations do not establish a latency SLA.
396
-
397
- Fee-inclusive, user-transfer Supply also prewarms when an eligible source is
398
- selected with an empty/zero amount. It constructs a provisional quote for at most
399
- one token, capped by the available balance, and retains only fee hints. The amount,
400
- fee preview and executable plan remain empty. Typing can await in-flight prewarming
401
- before validating the real amount; closing or changing wallet/configuration
402
- cancels it. Failed provisional preparation leaves normal quoting available.
403
- This does not apply to direct external actions, exact-output Pay, fee-only actions
404
- or actions with explicit funding inputs/proceeds. No signature or submission occurs
405
- during prewarming. The time available before typing determines how much work it hides.
406
-
407
- ### Fee funding for fee-only actions
408
-
409
- - A fee-only action (`feeOnlyAction`) runs on what the executing wallet already holds; the SDK funds only the Spicenet fees, in the chain's fee token (`destinationToken`).
410
- - With no `selectedAsset`, the SDK picks the fee source and the user is never asked. The action's input (`actionInput`) comes first, then its proceeds (`actionProceeds`), so the fee stays inside the action the user submitted; then other balances: same-chain embedded wallet before rollup, and within each, balances already in the fee token before balances that swap into it. The first source that simulates and covers the fee wins and is reported as `fees.fundingSource`; a source whose swap has no route, or that cannot cover the fee within slippage, is skipped for the next.
411
- - Declare `actionInput` for every token the action consumes and honour the amount passed to `buildActionCalls`: when fees come from an input, the action is built for the remainder. Declare `actionProceeds` for a token the action leaves in the wallet. Both are optional; with neither, only balances can pay. Omit `actionProceeds` when the output is delivered to another owner.
412
- - `feeFunding` overrides the level order per transaction, for example `["proceeds", "input", "balance"]`; the order inside the balance level is fixed. An empty list leaves the choice to the host's `selectedAsset`.
413
- - A `selectedAsset` on a fee-only action is an explicit host choice and funds the fee from that balance as before.
414
- - Account wallet withdrawals use this hierarchy automatically and display the chosen fee asset without a selector. The exact withdrawal amount is reserved before ranking balances; only its remainder can pay fees. Custom `useSpiceSupply` actions that must preserve an exact principal can declare `feeFundingReserve: { asset, amount }` instead of offering that principal as `actionInput`.
415
- - `feeOnlyAction` alone declares the action: `amountDirection` defaults to `"output"` and `paymentAmount` to `"0"`. Explicit values still win.
416
- - `submit()` on the controller runs the executor that matches `amountDirection`. `submitSupply()` and `submitPay()` remain for existing callers.
417
-
418
- ### Updating and releasing
419
-
420
- Change the shared inventory/policy/controller owner and audit affected stock and custom
421
- consumers. Use the central team's `workflow`, `ui-quality` and `shared/sdk.md` guidance;
422
- keep skills in the skills repo rather than copying them here.
423
-
424
- Read the central `shared/client-design-decisions.md` register before an upgrade. Map
425
- applicable decision IDs to affected consumers and regression scenarios, and report each
426
- result with exact candidate evidence. Preserve agreed behavior and ownership even when
427
- types compile; a deliberate change needs an explicit product decision and migration plan.
428
-
429
- Run focused regressions, `bun test`, `bun run typecheck` and `bun run build`, then validate
430
- affected consumer builds and rendered/financial behavior on the exact candidate. Record
431
- package identity, configuration, acceptance coverage and untested cases. Earlier candidate
432
- results do not automatically apply to newer main or a new client.
433
-
434
- If core/API changes are needed, resolve those dependencies before their UI/app consumers.
435
- An inventory/controller-only change does not inherently require a core release. After
436
- explicit release authorization, publish a prerelease with a non-`latest` tag, verify its
437
- registry artifact, and pin/test that exact version in each adopting app. Stable promotion,
438
- client deployment and docs targeting are separate release decisions. This refactor adds
439
- no automatic npm publishing.
440
-
441
- ### Building the Library
442
-
443
- ```bash
444
- bun run build
445
- ```
446
-
447
- ### Type Checking
448
-
449
- ```bash
450
- bun run typecheck
451
- ```
452
-
453
- CI runs this on every pull request, so a type error fails the build.
454
-
455
- ### Formatting
456
-
457
- ```bash
458
- bun run format
459
- ```
460
-
461
- Prettier runs over staged files on commit via lint-staged. There is no ESLint
462
- setup in this repository.
463
-
464
- ## Component Playground
465
-
466
- This repository includes a lightweight React playground app for viewing and testing all components without the complexity of Storybook.
467
-
468
- ### Running the Playground
469
-
470
- 1. Install dependencies (if not already done):
471
-
472
- ```bash
473
- bun install --frozen-lockfile
474
- ```
475
-
476
- 2. Navigate to the playground directory:
477
-
478
- ```bash
479
- cd playground
480
- ```
481
-
482
- 3. Install playground dependencies:
483
-
484
- ```bash
485
- bun install --frozen-lockfile
486
- ```
487
-
488
- 4. Start the development server:
489
-
490
- ```bash
491
- bun run dev
492
- ```
493
-
494
- The playground will open automatically at `http://localhost:3000`
495
-
496
- ### Using the Playground
497
-
498
- - **Left Sidebar**: Browse available components
499
- - **Main Area**: View the selected component with different example variants
500
- - **Example Tabs**: Switch between different states/examples for each component
501
-
502
- ### Adding a New Component Example
503
-
504
- To add a new example variant for a component:
505
-
506
- 1. Open `playground/src/ComponentRegistry.tsx`
507
- 2. Find the component's entry in the `examples` object
508
- 3. Add a new entry to the array:
509
- ```tsx
510
- {
511
- label: 'Your Example Name',
512
- render: () => <YourComponent {...props} />,
513
- }
514
- ```
515
-
516
- The playground will automatically pick up the new example and display it as a tab.
517
-
518
- ## License
519
-
520
- MIT
@@ -1,2 +0,0 @@
1
- "use client";
2
- "use strict";var Ge=Object.create;var fe=Object.defineProperty;var He=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var Ye=Object.getPrototypeOf,Qe=Object.prototype.hasOwnProperty;var Xe=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Ve(t))!Qe.call(e,o)&&o!==r&&fe(e,o,{get:()=>t[o],enumerable:!(n=He(t,o))||n.enumerable});return e};var Ke=(e,t,r)=>(r=e!=null?Ge(Ye(e)):{},Xe(t||!e||!e.__esModule?fe(r,"default",{value:e,enumerable:!0}):r,e));const C=require("react/jsx-runtime"),y=require("react"),B=require("@spicenet-io/spiceflow-core"),me=require("@tanstack/react-query"),v=require("viem"),T=require("@privy-io/react-auth"),pe=require("wagmi"),Ze=require("viem/actions");function J(e,t){return!e||!t?!1:B.isNativeToken(e)&&B.isNativeToken(t)?!0:v.isAddress(e)&&v.isAddress(t)?v.getAddress(e)===v.getAddress(t):e.toLowerCase()===t.toLowerCase()}const et="0xcA11bde05977b3631167028862bE2a173976CA11",tt={4114:"0x860aFc632a6D625B95d8B96f39ECC48E988c6962"},rt=new Set([5115]);function nt(e){const t=tt[e.id]??(rt.has(e.id)?void 0:et);return v.defineChain({id:e.id,name:e.name,nativeCurrency:e.nativeCurrency,rpcUrls:{default:{http:[e.rpcUrl]}},blockExplorers:{default:{name:e.displayName,url:e.blockExplorer}},testnet:e.network==="testnet",...t?{contracts:{multicall3:{address:t}}}:{}})}const at=[1,8453,42161,4114,4663,56,137,143,11155111,421614,84532,5115,123420001114,688688,688689,46630],G=Object.fromEntries(at.map(e=>B.getChainMeta(e)).filter(e=>e!=null).map(e=>[e.id,{...e,viemChain:nt(e),supportedTokens:B.getTokensForChain(e.id)}])),ot=G;let he={};const re=new Map,ge=e=>{he={...e||{}},re.clear()},H=(e,t=he)=>{const r=t[e];return r&&typeof r=="string"&&r.length>0?r:G[e]?.rpcUrl||void 0},M=e=>G[e],V=e=>{const t=B.getChainMeta(e);return t?.displayName||t?.name||`Chain ${e}`},st=e=>{const t=B.getDelegateContract(e);if(!t)throw new Error(`Unsupported chain ID: ${e}`);return t},it=(e,t,r)=>{const n=M(e);return n?.blockExplorer?`${n.blockExplorer}/tx/${t}`:"#"};async function lt(e,t){const r=`0x${t.toString(16)}`;try{await e.request({method:"wallet_switchEthereumChain",params:[{chainId:r}]});return}catch(a){if(!(a?.code===4902||typeof a?.message=="string"&&a.message.includes("Unrecognized chain ID")))throw a}const n=M(t);if(!n)throw new Error(`Cannot add unknown chain ${t} to wallet`);const o=H(t);if(!o)throw new Error(`Configure SpiceFlowProvider rpcOverrides[${t}] before adding this chain.`);await e.request({method:"wallet_addEthereumChain",params:[{chainId:r,chainName:n.name||`Chain ${t}`,nativeCurrency:n.nativeCurrency||{name:"ETH",symbol:"ETH",decimals:18},rpcUrls:[o],blockExplorerUrls:n.blockExplorer?[n.blockExplorer]:void 0}]})}const ye=(e,t)=>{const r=M(e);if(!r)throw new Error(`Unsupported chain ID: ${e}`);const n=H(e,t);if(!n)throw new Error(`No RPC URL configured for chain ID: ${e}`);const o=`${e}:${n}`,a=re.get(o);if(a)return a;const s=v.createPublicClient({chain:r.viemChain,transport:v.http(n,{timeout:15e3,retryCount:2,retryDelay:300}),batch:{multicall:{wait:16}}});return re.set(o,s),s},ct=e=>{const t={};return Object.values(G).forEach(r=>{const n=H(r.id,e);n&&(t[r.id]={name:r.name,chain:r.viemChain,rpcUrl:n})}),t},dt=(e,t=[])=>{const r=M(e);if(!r)return[];const n=[];if(![11155111,421614,688689].includes(e)){const a=t.find(s=>s.chainId===e&&s.isNative);a?n.push(a):n.push({address:B.ZERO_ADDRESS,name:r.nativeCurrency.name,symbol:r.nativeCurrency.symbol,decimals:r.nativeCurrency.decimals,balance:BigInt(0),balanceFormatted:0,balanceUsd:0,logoURI:void 0,isNative:!0,chainId:e})}if(!r.supportedTokens)return n;const o=n.some(a=>a.isNative);return r.supportedTokens.forEach(a=>{if(o&&B.isNativeToken(a.address))return;const s=t.find(l=>l.chainId===e&&J(l.address,a.address));s?n.push(s):n.push({address:a.address,name:a.name,symbol:a.symbol,decimals:a.decimals,balance:BigInt(0),balanceFormatted:0,balanceUsd:0,logoURI:a.logo,isNative:!1,chainId:e})}),n},ut=async(e,t,r)=>{const n=M(e);if(!n)throw new Error(`Unsupported chain ID: ${e}`);const o=t.toLowerCase(),a=n.supportedTokens?.find(l=>J(l.address,o));let s;if(B.isNativeToken(o)){if(n.nativeCurrency?.decimals==null)throw new Error(`Native currency decimals not configured for chain ${e}`);s=n.nativeCurrency.decimals}else if(a)s=a.decimals;else{if(!v.isAddress(t,{strict:!1}))throw new Error(`Could not determine decimals for invalid token address: ${t}`);const l=ye(e,r);try{s=await Ze.readContract(l,{address:v.getAddress(t),abi:v.erc20Abi,functionName:"decimals"})}catch(p){throw Object.assign(new Error(`Could not determine decimals for token ${t} on chain ${e}. Check the token contract and RPC configuration, then try again.`),{cause:p})}}if(!Number.isInteger(s)||s<0||s>255)throw new Error(`Invalid token decimals for ${t} on chain ${e}. Expected an integer from 0 to 255.`);return s},ft=e=>{const t=e?.wallet;if(t?.address&&t.walletClientType==="privy"&&t.connectorType==="embedded"&&!t.imported)return t.address},be=async(e,t)=>{if(!e?.address)throw new Error("Wallet not available");if(typeof e.getWalletClient=="function"){const r=t===void 0?[void 0]:[String(t),t,void 0];let n=null;for(const o of r)try{const a=await e.getWalletClient(o);if(a)return a}catch(a){n=a}if(n)throw n}if(typeof e.getEthereumProvider=="function"){const r=await e.getEthereumProvider(),n=t?M(t)?.viemChain:void 0;return v.createWalletClient({account:e.address,chain:n,transport:v.custom(r)})}throw new Error("Wallet client not available")},mt=async(e,t)=>{if(!e?.address)throw new Error("Wallet not available");if(typeof e.getEthereumProvider=="function")return e.getEthereumProvider();const r=await be(e,t);return{request:n=>r.request(n)}},Y=e=>e?.address?{address:e.address,wallet:e,getWalletClient:t=>be(e,t),getEthereumProvider:t=>mt(e,t)}:null,ne=()=>{const e=pe.useAccount(),t=e.status==="connected"?e.address:void 0,r=e.connector;let n=null,o=null;try{n=T.useWallets(),{user:o}=T.usePrivy()}catch{n=null,o=null}const a=n?.wallets||[],s=T.getEmbeddedConnectedWallet(a),l=s?.address?.toLowerCase(),p=a.find(g=>g.connectorType!=="embedded"&&g.address?.toLowerCase()!==l),c=t?.toLowerCase(),m=t&&r&&!(c&&l&&c===l)?{address:t,connector:r,getEthereumProvider:()=>r.getProvider()}:null,d=t&&r?{address:t,connector:r,getEthereumProvider:()=>r.getProvider()}:null,f=Y(s),u=Y(m)||Y(p),w=f?.address||ft(o),i=Y(d)||u;return{embeddedWallet:f,externalWallet:u,embeddedAddress:w,externalAddress:u?.address,activeWallet:i,activeAddress:i?.address}},pt={base:0,raised:1,sticky:10,overlay:20,popover:40,modal:1e3,dropdown:1100,toast:1200,tooltip:1300},ht={none:"0",sm:"4px",md:"6px",card:"8px",input:"10px",shell:"9px",modal:"12px",pill:"9999px",circle:"50%"},F={out:"cubic-bezier(0.22, 1, 0.36, 1)",inOut:"cubic-bezier(0.65, 0, 0.35, 1)",linear:"linear"},P={fast:"120ms",normal:"150ms",slow:"300ms",progress:"400ms"},gt={interactive:`transform ${P.normal} ${F.out}, box-shadow ${P.normal} ${F.out}, background-color ${P.normal} ${F.out}, border-color ${P.normal} ${F.out}, color ${P.normal} ${F.out}`,color:`background-color ${P.normal} ${F.out}, border-color ${P.normal} ${F.out}, color ${P.normal} ${F.out}`,popover:`opacity ${P.normal} ${F.out}, transform ${P.normal} ${F.out}`},Q={ui:'"Helvetica Neue", sans-serif',mono:'"IBM Plex Mono", monospace'};class R extends Error{constructor(t,r,n={}){super(r),this.code=t,this.context=n,this.name="SpiceflowError",Error.captureStackTrace&&Error.captureStackTrace(this,R)}}function ae(e){return e instanceof R}const yt=["user rejected","user denied","user cancelled","rejected the request","action_rejected","4001"];function we(e){if(!e)return"";if(typeof e=="string")return e;const t=e,r=t.message||t.reason||"",n=t.details||"";return`${r} ${n}`.trim()}const ve=e=>{if(ae(e))return e.code==="WALLET_REJECTED";const t=we(e).toLowerCase();return yt.some(r=>t.includes(r))};let oe=null;function bt(e){return oe?.message===e?oe.detail:void 0}const wt=(e,t="Something went wrong. Please try again.")=>{if(ve(e))return"Transaction was rejected.";if(ae(e)){const r=e.message||t,{requestId:n,httpStatus:o,responseBody:a,endpoint:s}=e.context;return(n||o||s)&&(oe={message:r,detail:{requestId:n,httpStatus:o,responseBody:a,endpoint:s}}),r}return we(e)||t},vt=[...B.DELEGATE_ABI,...B.ERC20_ABI,...B.spicenetEscrowAbi,...B.uniV3SwapRouterAbi];function se(e,t=0){return t>4||e==null?[]:typeof e=="string"?e.match(/0x[0-9a-fA-F]{8,}/g)??[]:Array.isArray(e)?e.flatMap(r=>se(r,t+1)):typeof e=="object"?Object.values(e).flatMap(r=>se(r,t+1)):[]}function xe(e){try{const t=v.decodeErrorResult({abi:vt,data:e});if(t.errorName==="Error")return String(t.args?.[0]??"");if(t.errorName==="Panic")return`Panic(${t.args?.[0]})`;if(t.errorName==="CallReverted"){const r=t.args?.[4],n=r&&r!=="0x"?xe(r):void 0;return n?`Call reverted: ${n}`:"Call reverted (no reason returned)"}return t.errorName}catch{try{const t=v.hexToString(e).replace(/\x00+$/g,"").trim();return/^[\x20-\x7e]+$/.test(t)&&t.length>1?t:void 0}catch{return}}}function xt(e){for(const t of se(e)){if(!v.isHex(t))continue;const r=xe(t);if(r)return r}}function Ee(e,t){const r=t.match(/Output amount (\d+) is insufficient after fees (\d+) on chain (\d+)/);if(!r)return t;const[,n,o,a]=r,s=Number(a),l=BigInt(n),p=BigInt(o);let c;for(const x of e.intents){const b=x.chainBatches.find(h=>h.chainId===s);if(b){c=b;break}}const m=c?.tokenTransfers.find(x=>x.from==="escrow"&&J(x.to,e.user)&&!B.isNativeToken(x.token)),d=V(s);if(!m)return`Amount is too small to cover the estimated fee on ${d}.`;const f=B.getTokenByAddress(m.token,s),u=f?.decimals??18,w=f?.symbol??"tokens",i=v.formatUnits(l,u),g=v.formatUnits(p,u);return`Amount is too small to cover the estimated fee on ${d}. Amount: ${i} ${w}. Estimated fee: ${g} ${w}.`}function Et(e){return e?.code===4902||e?.name==="ChainNotConfiguredError"?"This network is not configured in your wallet. Please add it manually to continue.":e?.code===4001?"Network switch cancelled. Please switch manually in your wallet to continue.":"Unable to switch network. Please switch manually in your wallet to continue."}const Ct="https://tx-submission-api.spicenet.io",Ce=Ct;function $t(e,t){return typeof t=="bigint"?t.toString():t}function O(e,t=""){if(Array.isArray(e))e.forEach((r,n)=>O(r,`${t}[${n}]`));else if(e&&typeof e=="object")for(const[r,n]of Object.entries(e)){const o=t?`${t}.${r}`:r;if(!n&&(r==="user"||r==="to"||r==="from"||r==="address"))throw new R("INVALID_PARAMS",`Wallet not ready: ${o} is missing from the request. Wait a moment and retry, or log in again.`,{path:o});O(n,o)}}async function X(e,t){const r={...t?.headers},n=(t?.method??"GET").toUpperCase();(t?.body!=null||n!=="GET"&&n!=="HEAD")&&(r["x-request-id"]??(r["x-request-id"]=crypto.randomUUID()));const o=await fetch(e,{...t,headers:r}),a=o.headers.get("x-request-id")??"";return Object.assign(o,{requestId:a})}function j(e,t){if(!e)return t;const r=e.error,n=(typeof e.message=="string"?e.message:"")||(r&&typeof r=="object"&&"message"in r&&typeof r.message=="string"?r.message:"")||(typeof r=="string"?r:"")||t,o=xt(e);return o&&!n.includes(o)?`${n} \u2014 ${o}`:n}async function kt(e){try{return await e.json()}catch{return null}}function Bt(e){return(Array.isArray(e?.balances)?e.balances:[]).flatMap(t=>{if(!t||typeof t!="object")return[];const r=t;return[{receiptToken:typeof r.receiptToken=="string"?r.receiptToken:void 0,tokenName:String(r.tokenName??""),balanceAvailable:String(r.balanceAvailable??"0"),chainTokens:Array.isArray(r.chainTokens)?r.chainTokens.filter(n=>typeof n=="string"):r.chainTokens&&typeof r.chainTokens=="object"?r.chainTokens:r.chainTokenConfig&&typeof r.chainTokenConfig=="object"?r.chainTokenConfig:void 0,decimals:typeof r.decimals=="number"?r.decimals:void 0,balanceUsd:typeof r.balanceUsd=="number"||typeof r.balanceUsd=="string"?r.balanceUsd:null}]})}class At{constructor(){this.baseUrl=Ce,this.feeValidationAvailable=!1}setBaseUrl(t){t!==this.baseUrl&&(this.feeValidationAvailable=!1),this.baseUrl=t}getBaseUrl(){return this.baseUrl}async requestJson(t,r){const n=`${this.baseUrl}${t}`,o=await X(n,r.method&&r.method!=="GET"?{method:r.method,headers:{"Content-Type":"application/json"},body:JSON.stringify(r.body,$t),signal:r.signal}:{signal:r.signal}),a=await o.json();if(!o.ok||a.success===!1||r.ok&&!r.ok(a)){const s=j(a,r.fallbackError(o.status));throw new R(r.code??"RELAYER_ERROR",r.mapError?r.mapError(s):s,{httpStatus:o.status,requestId:o.requestId,responseBody:JSON.stringify(a),endpoint:t,...r.context})}return a}async createLimitOrder(t){return this.requestJson("/orders",{method:"POST",body:t,fallbackError:r=>`Order registration failed: ${r}`})}async getLimitOrder(t){return this.requestJson(`/orders/${encodeURIComponent(t)}`,{fallbackError:r=>`Order lookup failed: ${r}`})}async listLimitOrders(t,r){const n=new URLSearchParams({user:t,...r?{after:r}:{}});return this.requestJson(`/orders?${n}`,{fallbackError:o=>`Order listing failed: ${o}`})}async cancelLimitOrder(t,r){return this.requestJson(`/orders/${encodeURIComponent(t)}/cancel`,{method:"POST",body:{signature:r},fallbackError:n=>`Order cancellation failed: ${n}`})}async createAction(t){return O(t),this.requestJson("/actions",{method:"POST",body:t,fallbackError:r=>`Relayer API error: ${r}`})}async estimateActionFees(t,r){O(t);const n=this.baseUrl,o=await this.requestJson("/actions/fees/estimate",{method:"POST",body:t,signal:r?.signal,fallbackError:a=>`Relayer fee estimate error: ${a}`,ok:a=>!!a.success&&!!a.data?.feeEstimate,mapError:a=>Ee(t,a)});return n===this.baseUrl&&(this.feeValidationAvailable=o.data?.supportsFeeValidation===!0),o.data.feeEstimate}canValidateActionFees(){return this.feeValidationAvailable}async validateActionFees(t,r){return O(t),(await this.requestJson("/actions/fees/validate",{method:"POST",body:t,signal:r?.signal,fallbackError:n=>`Relayer fee validation error: ${n}`,ok:n=>!!n.success&&!!n.data?.feeEstimate,mapError:n=>Ee(t,n)})).data.feeEstimate}async estimateBatchGas(t){O(t);const{signal:r,...n}=t,o=await this.requestJson("/actions/gas/estimate",{method:"POST",body:n,signal:r,fallbackError:a=>`Relayer gas estimate error: ${a}`,ok:a=>!!a.success&&!!a.data?.gasFee});return{gasFee:BigInt(o.data.gasFee),gasQuoteSource:o.data.gasQuoteSource,simulationVerified:o.data.simulationVerified===!0,platformFee:o.data.platformFee===void 0?void 0:BigInt(o.data.platformFee)}}async executeStep(t,r,n,o){const a=`${this.baseUrl}/actions/${t}/intents/${r}/steps/${n}`,s=`/actions/${t}/intents/${r}/steps/${n}`,l=crypto.randomUUID(),p=3e4,c=new Promise((f,u)=>setTimeout(()=>u(new R("RELAYER_EXECUTE_ERROR",`Execute step request timed out after ${p/1e3}s`,{actionId:t,intentIndex:r,stepIndex:n,endpoint:s,requestId:l})),p)),m=await Promise.race([X(a,{method:"POST",headers:{"Content-Type":"application/json","x-request-id":l},body:JSON.stringify(o)}),c]),d=await m.json();if(!m.ok||d.success===!1)throw new R("RELAYER_EXECUTE_ERROR",j(d,`Execute step error: ${m.status}`),{httpStatus:m.status,requestId:m.requestId,responseBody:JSON.stringify(d),endpoint:s,actionId:t});if(d?.status==="error"||d?.status==="reverted"){const f=j(d,""),u=f?`: ${f}`:"";throw new R("RELAYER_EXECUTE_ERROR",`Step ${n} previously ${d.status}${u}`,{httpStatus:m.status,requestId:m.requestId,responseBody:JSON.stringify(d),endpoint:s,actionId:t,intentIndex:r,stepIndex:n,stepStatus:d.status})}return{success:!0,status:d.status,transactionHash:d.transactionHash}}async checkStepStatus(t,r){const n=t.split("/"),o=n[0],a=n.length>=2?n[1]:"0",s=await this.requestJson(`/actions/${o}/intents/${a}/steps/${r}`,{fallbackError:l=>`Intent step status check failed: ${l}`,code:"RELAYER_STATUS_ERROR",context:{intentId:t}});return{success:!0,data:{status:s.status==="error"?"reverted":s.status,transactionHash:s.txid,error:j(s,"")}}}async createWallet(t,r){return this.requestJson(`/wallets/${t}`,{method:"PUT",body:r,fallbackError:n=>`Create wallet error: ${n}`})}async getWallet(t){return this.requestJson(`/wallets/${t}`,{fallbackError:r=>`Get wallet error: ${r}`})}async createWithdrawal(t){return this.requestJson("/withdrawals",{method:"POST",body:t,fallbackError:r=>`Create withdrawal error: ${r}`})}async getWithdrawal(t){return this.requestJson(`/withdrawals/${t}`,{fallbackError:r=>`Get withdrawal error: ${r}`})}async getRollupTimestamp(){return this.requestJson("/time",{fallbackError:t=>`Get rollup timestamp error: ${t}`})}}const W=new At;async function St(e){const t=`${W.getBaseUrl()}/wallets/${e}/balance`,r=await X(t,{cache:"no-store"}),n=await kt(r);if(!r.ok||n?.success===!1)throw new R("RELAYER_ERROR",j(n,`Unable to verify Spice balance: ${r.status}`),{httpStatus:r.status,requestId:r.requestId,responseBody:JSON.stringify(n),endpoint:`/wallets/${e}/balance`});return Bt(n)}const ie=new Map,K=new Set;function le(e){return JSON.stringify([W.getBaseUrl(),e.toLowerCase()])}function D(e,t){if(le(e)!==t)throw new Error("Wallet backend changed during registration. Please retry.")}function It(e){if(!(e instanceof R))return!1;const t=e.context.httpStatus;return t===400||t===404?`${e.message} ${String(e.context.responseBody??"")}`.toLowerCase().includes("not found"):!1}async function $e(e){const t=le(e);if(K.has(t))return!0;try{return await W.getWallet(e),D(e,t),K.add(t),!0}catch(r){if(!It(r))throw r;return!1}}async function ke(e,t){const r=le(e);if(K.has(r))return;const n=ie.get(r);if(n)return n;const o=(async()=>{const a=await $e(e);if(D(e,r),a)return;const{unix_timestamp:s}=await W.getRollupTimestamp();D(e,r);const l=`I am creating a new smart wallet and adding an admin wallet ethereum:0x${e.slice(2).toLowerCase()}. Nonce: ${s}`,p=await t(l);D(e,r);const c=typeof p=="string"?p:p.signature;if(!c)throw new Error("Wallet returned no smart wallet signature");await W.createWallet(e,{timestamp:s,signature:c}),D(e,r),K.add(r)})();ie.set(r,o);try{await o}finally{ie.delete(r)}}const Tt=(e,t)=>{const r=o=>t?t(o):V(o),n=[];return e.forEach((o,a)=>{const s=o.calls&&o.calls.length>0,l=o.tokenTransfers&&o.tokenTransfers.length>0;!s&&!l||n.push({stepId:a,status:a===0?"processing":"pending",chainId:o.chainId,chainName:r(o.chainId),description:"Processing transaction"})}),n},Be=()=>{const e=y.useContext(ee),t=e?.mode,{isConnected:r,address:n}=pe.useAccount();let o=null,a=null,s=null,l=null,p=null;try{o=T.usePrivy(),a=T.useSign7702Authorization(),s=T.useSignMessage(),l=T.useWallets()}catch(h){p=h instanceof Error?h.message:String(h)}if(p)return t==="ondemand"&&r&&n?{isReady:!0,isAuthenticated:!0,isConnected:!0,address:n,provider:"privy",actions:{signAuthorization:async()=>{throw new Error("Not available in ondemand mode")},signMessage:async()=>{throw new Error("Not available in ondemand mode")},getEthereumProvider:async()=>{throw new Error("Not available in ondemand mode")}}}:{isReady:!1,isAuthenticated:!1,isConnected:!1,provider:null,actions:{signAuthorization:async()=>{throw new Error("Privy not available")},signMessage:async()=>{throw new Error("Privy not available")},getEthereumProvider:async()=>{throw new Error("Privy not available")}}};const{ready:c=!1,authenticated:m=!1}=o||{},d=e?.embeddedWalletAddress,f=T.getEmbeddedConnectedWallet(l?.wallets||[]),u=d||f?.address,w=!!u&&!!f,i={isReady:t==="ondemand"?!0:c,isAuthenticated:t==="ondemand"?!0:m||w,isConnected:t==="ondemand"?r&&!!n||c&&(m||w)&&w:c&&w,address:t==="ondemand"&&r?n:u,provider:"privy"},g=async(h,$)=>{if(!f)throw new Error("Embedded wallet not available");const k=await f.getEthereumProvider(),{createWalletClient:S,custom:E}=await import("viem"),N=S({account:f.address,transport:E(k)});return $?.(),{signature:await N.signMessage({account:f.address,message:h})}},x=async h=>{if(s)try{return{signature:(await s.signMessage({message:h})).signature}}catch($){if(!f)throw $}if(!f)throw new Error("Privy sign message not available");return g(h)},b=async()=>{if(!u)throw new Error("Embedded wallet not available for smart wallet setup");await ke(u,x)};return{...i,actions:{prepareSigning:async()=>{if(!u)throw new Error("Embedded wallet not available");await $e(u)},signAuthorization:async(h,$)=>{if(!a)throw new Error("Privy sign authorization not available");try{await b(),$?.();const k=await a.signAuthorization({contractAddress:h.contractAddress,chainId:h.chainId,nonce:h.nonce},{address:u});return delete k.v,k}catch(k){const S=typeof k?.message=="string"?k.message:String(k?.message??k??"");throw S.includes("not configured")||S.includes("chain")?new Error(`Chain ${h.chainId} is not configured in PrivyProvider. Add this chain to your Privy dashboard or supportedChainIds.`):k}},signMessage:async(h,$)=>{if(typeof h=="object"&&"raw"in h){if(await b(),!f)throw new Error("Embedded wallet not available for raw signing");return g(h,$)}return $?.(),x(h)},getEthereumProvider:async()=>{if(!f)throw new Error("Embedded wallet not available");return f.getEthereumProvider()}}}},Ft="https://portal-api.spicenet.io/api/v1",ce=e=>e.replace(/\/+$/,""),Pt=(e,t)=>`${e}:${t}`;async function Rt(e,t){const r=new URLSearchParams({walletAddress:t,campaignId:"spicenet-portal",campaignGroup:"1"}),n=await fetch(`${e}/portal/mainnet/linked-portal-wallet?${r.toString()}`,{cache:"no-store"});if(!n.ok){if(n.status>=500){const o=new Error(`Failed to resolve linked portal wallet: ${n.status}`);throw o.status=n.status,o}return null}return(await n.json()).portalWalletAddress?.toLowerCase()??null}async function Nt(e,t,r){const n=new URLSearchParams({walletAddress:t,campaignId:r.campaignId,campaignGroup:r.campaignGroup,partnerId:r.partnerId}),o=await fetch(`${e}/portal/mainnet/whitelist/check?${n.toString()}`,{cache:"no-store"});if(!o.ok){const a=new Error(`Failed to check whitelist: ${o.status}`);throw a.status=o.status,a}return await o.json()}async function Wt(e,t,r,n){const o=await fetch(`${e}/portal/mainnet/link-wallet`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({campaignId:t.campaignId,campaignGroup:t.campaignGroup,partnerId:t.partnerId,externalWalletAddress:r,embeddedWalletAddress:n})});if(!o.ok)throw new Error(`Failed to link wallet: ${o.status}`)}function Ae({walletAddress:e,campaignId:t,campaignGroup:r,partnerId:n,apiBaseUrl:o=Ft,enabled:a=!0,staleTime:s=5*60*1e3,refetchInterval:l=!1}={}){const{isAuthenticated:p,isConnected:c}=Be(),{externalAddress:m,embeddedAddress:d}=ne(),f=e??m,u=typeof f=="string"?f.toLowerCase():void 0,w=typeof d=="string"?d.toLowerCase():void 0,i=typeof m=="string"?m.toLowerCase():void 0,g=a&&p&&c&&!!m&&!!u&&v.isAddress(u)&&!!t&&!!r&&!!n,x=y.useMemo(()=>["partner-whitelist","check",u,t,r,n,ce(o)],[u,t,r,n,o]),b=me.useQuery({queryKey:x,enabled:g,staleTime:s,refetchInterval:l,retry:(E,N)=>{const I=N?.status;return typeof I=="number"&&I>=400&&I<500?!1:E<3},retryDelay:E=>Math.min(1e3*2**E,15e3),queryFn:async()=>{if(!u||!v.isAddress(u))throw new Error("A valid wallet address is required");if(!t||!r||!n)throw new Error("campaignId, campaignGroup, and partnerId are required");const E=ce(o),N={campaignId:t,campaignGroup:r,partnerId:n},I=await Rt(E,u);return I?Nt(E,I,N):{walletAddress:u,campaignId:t,campaignGroup:r,partnerId:n,isWhitelisted:!1,isLinkedWallet:!1,whitelistedWalletAddress:null,externalWalletAddress:u}}}),h=a&&p&&c&&!!i&&v.isAddress(i)&&!!w&&v.isAddress(w)&&!!t&&!!r&&!!n,$=me.useMutation({mutationFn:E=>Wt(ce(o),{campaignId:t,campaignGroup:r,partnerId:n},E.external,E.embedded)}),k=y.useRef(new Set),S=$.mutate;return y.useEffect(()=>{if(!h||!i||!w||!t||!r||!n)return;const E=Pt(i,w);k.current.has(E)||(k.current.add(E),S({external:i,embedded:w}))},[h,i,w,t,r,n,S]),{isWhitelisted:b.data?.isWhitelisted??!1,whitelistCheck:b.data,checkedWalletAddress:u,externalWalletAddress:m,embeddedWalletAddress:d,isExternalWalletConnected:!!m,isWhitelistCheckEnabled:g,linkWallet:$,isWalletLinked:$.isSuccess,...b}}const Se={assets:[],loading:!1,refreshing:!1,error:null,updatedAt:null};function Ie(){const e=new Map,t=new Map,r=new WeakMap;let n=0;function o(){let a=Se;const s=new Map;let l,p=null,c=0,m=!1,d;const f=()=>s.forEach((i,g)=>g()),u=()=>{if(!d||!s.size)return Promise.resolve();if(p)return p;const i=++c;return a={...a,loading:a.updatedAt===null,refreshing:!0},f(),p=Promise.resolve().then(d).then(g=>{if(i!==c)return;const{assets:x,error:b=null}=Array.isArray(g)?{assets:g}:g;a={assets:x,loading:!1,refreshing:!1,error:b,updatedAt:Date.now()}},g=>{i===c&&(a={...a,loading:!1,refreshing:!1,error:g instanceof Error?g.message:"Unable to refresh balances"})}).finally(()=>{i===c&&(p=null,f(),m&&(m=!1,u()))}),p},w=()=>{clearInterval(l);const i=[...s.values()].filter(g=>g>0);i.length&&(l=setInterval(()=>void u(),Math.min(...i)))};return{snapshot:()=>a,refresh:u,invalidate:()=>p?(m=!0,p.then(()=>p??Promise.resolve())):u(),subscribe(i,g,x){return d=g,s.set(i,x),w(),u(),()=>{s.delete(i),w(),s.size||(c++,p=null,m=!1,a={...a,loading:!1,refreshing:!1})}}}}return{entry(a){let s=e.get(a);return s||(s=o(),e.set(a,s)),s},fetcherId(a){return r.has(a)||r.set(a,++n),r.get(a)},registerTokens(a){let s=!1;for(const l of a){const p=`${l.chainId}:${l.address.toLowerCase()}`;t.has(p)||(t.set(p,l),s=!0)}s&&e.forEach(l=>l.invalidate())},tokens:()=>[...t.values()],invalidate:()=>Promise.all([...e.values()].map(a=>a.invalidate())).then(()=>{})}}function Te(e,t){return e<=0n||t<=0n?0n:(e*t+10000n-1n)/10000n}function Ut(e,t,r){const n=(e>t?e-t:0n)*10000n/(10000n+r),o=Te(n,r),a=n>0n,s=a?o+t:0n;return{netAmount:n,spicenetFee:a?o:0n,totalFee:s,grossAmount:n+s,coversFees:a}}function Lt(e,t,r){if(e===t)return!0;const n=e>t?e:t;return n===0n?!1:(e>t?e-t:t-e)*10000n<=n*r}function de(e=4e3){if(!Number.isInteger(e)||e<0||e>1e4)throw new Error("feeToleranceBps must be an integer between 0 and 10000");return e}function Fe(e,t,r){const n=BigInt(de(r));return(e>t?e-t:t-e)*10000n<=e*n}function Mt(e,t,r){return["gasFee","spicenetFee","bridgeFee"].every(n=>Fe(e[n]??0n,t[n]??0n,r))}function qt(e,t){const r=v.formatUnits(e,t);return r.includes(".")?r.replace(/\.?0+$/,"")||"0":r}function Z(e){const t=BigInt(e.feeBreakdown.spicenetFee),r=BigInt(e.feeBreakdown.gasFee),n=BigInt(e.feeBreakdown.bridgeFee??"0"),o=t+r+n;return{spicenetFee:t,gasFee:r,bridgeFee:n,totalFee:o}}function Ot(e){const t=BigInt(e.quotedAmount),r=BigInt(e.totalDebit),{totalFee:n}=Z(e);return t>0n||r>0n||n>0n}function jt(e){const t=BigInt(e.quotedAmount),r=BigInt(e.totalDebit),{totalFee:n}=Z(e),o=t+n;return r>o?r:o}function Dt(e){const t=[];for(const r of e.intents)for(const n of r.batches)t.push(BigInt(n.quotedAmount));return t.length!==1?null:t[0]}function _t(e,t,r){return e.length===0||!e.every(n=>n.domain!=="spicenet"&&!n.tokenTransfers?.length)?null:{...t,grossAmount:r,netAmount:r,totalFee:0n,gasFee:0n,spicenetFee:0n,bridgeFee:0n,gasQuoteSource:"static_zero"}}function zt(e,t){const r=e.intents.flatMap(m=>m.batches).filter(Ot);if(r.length===0)return null;const n=r.filter(m=>m.chainId===t.chainId),o=n.length>0?n:r;if(o.length>1)return null;const a=o[0],{gasFee:s,spicenetFee:l,bridgeFee:p,totalFee:c}=Z(a);return{chainId:a.chainId,tokenAddress:t.tokenAddress,symbol:t.symbol,decimals:t.decimals,grossAmount:jt(a),netAmount:BigInt(a.quotedAmount),totalFee:c,gasFee:s,spicenetFee:l,bridgeFee:p,gasQuoteSource:a.gasQuoteSource}}const Pe=(e,t=4)=>!isFinite(e)||e===0?"0":e>0&&e<1e-6?e.toFixed(12).replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,""):e<1e-4?e.toFixed(8).replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,""):e.toFixed(t).replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,""),Jt=e=>isFinite(e)?e>0&&e<.01?"<$0.01":new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:2}).format(e):"$0.00",Re=(e,t=6,r=4)=>e.length<=t+r?e:`${e.slice(0,t)}...${e.slice(-r)}`,Gt=(e,t)=>{const r=e.replace(/[^0-9.]/g,"").split("."),n=r[0]??"",o=r.slice(1).join(""),a=typeof t=="number"?Math.max(0,Math.min(t,18)):null;return r.length===1?n:`${n}.${a===null?o:o.slice(0,a)}`},Ht=(e,t=6)=>!Number.isFinite(e)||e<=0?"0":e.toFixed(Math.min(Math.max(t,0),18)).replace(/\.?0+$/,""),Vt=async e=>{try{return await navigator.clipboard.writeText(e),!0}catch(t){return console.error("Failed to copy to clipboard:",t),!1}};function Yt(e,t){return t?.length?t.some(r=>{const n=r.toLowerCase();return n===e.symbol.toLowerCase()||B.normalizeNativeTokenAddress(n).toLowerCase()===B.normalizeNativeTokenAddress(e.address).toLowerCase()||n===e.displaySymbol?.toLowerCase()||n===e.receiptTokenName?.toLowerCase()||n==="native"&&(e.isNative||B.isNativeToken(e.address))}):!0}function Ne(e){return e.displaySymbol||e.symbol}function We(e){return e.displayName||e.displaySymbol||e.name||e.symbol}function Qt(e,t){const r=e.location==="rollup"||e._source==="spice"||!!e.receiptTokenId&&!e.location&&!e._source,n=a=>a&&t?Object.entries(t).find(([s])=>s.toLowerCase()===a.toLowerCase())?.[1]:void 0,o=r?n(e.receiptTokenId)??n(e.receiptTokenName):void 0;return{symbol:o?.symbol||o?.name||Ne(e),name:o?.name||o?.symbol||We({...e,name:e.name||""}),logoURI:o?.logoURI===void 0?e.logoURI:o.logoURI||null}}function Xt(e){return e.displayChainName||V(e.chainId)}function Kt(e){if(e.balance===0n)return"0";const t=v.formatUnits(e.balance,e.decimals),r=Pe(Number(t));return Number(r)===0?t:r}const Ue=y.createContext(void 0);function Zt(){const e=y.useContext(Ue);return y.useCallback(t=>Qt(t,e),[e])}const er=["__spicenet_whitelist_blocked__"],Le="spiceflow-mode",Me=()=>{if(typeof window>"u")return null;try{const e=window.localStorage.getItem(Le);return e==="7702"?"7702":e==="non7702"?"ondemand":e==="embedded"?"7702":e==="external"?"ondemand":null}catch{return null}},tr=e=>{if(!(typeof window>"u"))try{window.localStorage.setItem(Le,e==="7702"?"7702":"non7702")}catch{}},rr=()=>Me()!==null,qe=y.createContext(null),ee=y.createContext(null),nr=({children:e,value:t,whitelist:r})=>{const n=!!r&&r.enabled!==!1,{isWhitelisted:o,isLoading:a,whitelistCheck:s}=Ae(n?r:{enabled:!1}),l=n&&!o,p=y.useMemo(()=>({...t,mode:l?"ondemand":t.mode,allowedTokens:l?er:t.allowedTokens,isUserWhitelisted:o,isWhitelistChecking:a,isWhitelistConfigured:n,isWhitelistGateActive:l,whitelistCheck:s}),[t,n,l,o,a,s]);return C.jsx(ee.Provider,{value:p,children:e})},ar=({feeToleranceBps:e,children:t,provider:r="privy",supportedChainIds:n,allowedTokens:o,network:a="testnet",mode:s,skipFlow:l=[],nativeChainId:p,appName:c="Spicenet",apiUrl:m,theme:d,rpcOverrides:f,swapper:u,liquidityVenues:w,additionalSourceTokens:i,whitelist:g,assetDisplay:x})=>{const b=de(e);y.useEffect(()=>{if(!m)return;const q=W.getBaseUrl();return W.setBaseUrl(m),()=>W.setBaseUrl(q)},[m]),y.useEffect(()=>(ge(f),()=>ge(void 0)),[f]);const h=y.useMemo(()=>B.getChainIdsByNetwork(a),[a]),$=n??h,k=d??null,[S,E]=y.useState(()=>s??Me()??"7702");y.useEffect(()=>{s&&E(s)},[s]);const N=y.useCallback(q=>{if(s){E(s);return}E(q),tr(q)},[s]),{embeddedAddress:I,externalAddress:A}=ne(),z=JSON.stringify(f),U=y.useMemo(Ie,[a,m,z,r]),L=y.useMemo(()=>({feeToleranceBps:b,inventory:U,provider:r,supportedChainIds:$,allowedTokens:o,skipFlow:l,mode:S,setMode:N,network:a,nativeChainId:p,appName:c,apiUrl:m,rpcOverrides:f,swapper:u,liquidityVenues:w,additionalSourceTokens:i,embeddedWalletAddress:I,externalWalletAddress:A,isUserWhitelisted:null,isWhitelistChecking:!1,isWhitelistConfigured:!1,isWhitelistGateActive:!1}),[U,b,r,$,o,l,S,N,a,p,c,m,f,u,w,i,I,A]);return C.jsx(qe.Provider,{value:k,children:C.jsx(ee.Provider,{value:L,children:C.jsx(nr,{value:L,whitelist:g,children:C.jsx(Ue.Provider,{value:x,children:t})})})})},or={spacing:{xs:"0.25rem",sm:"0.5rem",md:"1rem",lg:"1.5rem",xl:"2rem"},borderRadius:{sm:"0.25rem",md:"0.5rem",lg:"0.75rem",full:"9999px"},typography:{fontFamily:Q.ui,fontSize:{xs:"0.75rem",sm:"0.875rem",base:"1rem",lg:"1.125rem",xl:"1.25rem","2xl":"1.5rem","3xl":"1.75rem"},fontWeight:{normal:400,medium:500,semibold:600,bold:700}},shadows:{sm:"0 1px 2px 0 rgb(0 0 0 / 0.05)",md:"0 4px 6px -1px rgb(0 0 0 / 0.1)",lg:"0 10px 15px -3px rgb(0 0 0 / 0.1)"},animation:{fast:"150ms ease-in-out",normal:"200ms ease-in-out",slow:"300ms ease-in-out"}},sr={primary:"#EA4B4B",primaryHover:"#E43838",secondary:"#f3f4f6",background:"#ffffff",surface:"#f9fafb",surfaceHover:"#f3f4f6",border:"#e5e7eb",borderHover:"#d1d5db",text:"#111827",textSecondary:"#374151",textMuted:"#6b7280",success:"#10b981",error:"#ef4444",warning:"#f59e0b",info:"#3b82f6"},ir={primary:"#EA4B4B",primaryHover:"#E43838",secondary:"#171717",background:"#141414",surface:"#1e1e1e",surfaceHover:"#2a2a2a",border:"#2d2d2d",borderHover:"#3a3a3a",text:"#ffffff",textSecondary:"#888888",textMuted:"#666666",success:"#10b981",error:"#ef4444",warning:"#f59e0b",info:"#3b82f6"},Oe=(e="light")=>({mode:e,...or,colors:e==="light"?sr:ir});function te(e,t){const r=e.replace("#",""),n=Math.max(0,Math.round(parseInt(r.substring(0,2),16)*(1-t))),o=Math.max(0,Math.round(parseInt(r.substring(2,4),16)*(1-t))),a=Math.max(0,Math.round(parseInt(r.substring(4,6),16)*(1-t)));return`#${n.toString(16).padStart(2,"0")}${o.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}`}function lr(e,t){return{shell:t?.shell??"#141414",shellBorder:t?.shellBorder??te(t?.shell??"#141414",.35),shellInnerBorder:t?.shellInnerBorder??te(t?.shell??"#141414",.55),cardBg:t?.cardBg??"#1e1e1e",inputBg:t?.inputBg??"#171717",hoverBg:t?.hoverBg??"#2a2a2a",textPrimary:t?.textPrimary??"#ffffff",textSecondary:t?.textSecondary??"#888888",inputText:t?.inputText??"#e0e0e0",inputPlaceholder:t?.inputPlaceholder??"#555555",cardBorder:t?.cardBorder??"rgba(255,255,255,0.08)",inputBorder:t?.inputBorder??"rgba(255,255,255,0.10)",buttonBorder:t?.buttonBorder??"rgba(255,255,255,0.12)",successBg:t?.successBg??"#0a1f18",successBorder:t?.successBorder??"#1a4a3a",successText:t?.successText??"#6ee7b7",warningBg:t?.warningBg??"#1f1a0d",warningBorder:t?.warningBorder??"#4a3820",warningText:t?.warningText??"#fbbf24",errorBg:t?.errorBg??"#1f0d0d",errorBorder:t?.errorBorder??"#4a2020",errorText:t?.errorText??"#f87171",infoBg:t?.infoBg??"#0d1325",infoBorder:t?.infoBorder??"#2a3a5a",infoText:t?.infoText??"#60a5fa"}}function cr(e){return{shell:"#ffffff",shellBorder:"#E9E9E9",shellInnerBorder:"#D5D5D5",cardBg:"#f9fafb",inputBg:"#ffffff",hoverBg:"#f3f4f6",textPrimary:"#111827",textSecondary:"#6b7280",inputText:"#111827",inputPlaceholder:"#9ca3af",cardBorder:"#e5e7eb",inputBorder:"#d1d5db",buttonBorder:"#0e0d0b",successBg:"#d1fae5",successBorder:"#6ee7b7",successText:"#065f46",warningBg:"#fef3c7",warningBorder:"#fde68a",warningText:"#92400e",errorBg:"#fef2f2",errorBorder:"#fecaca",errorText:"#dc2626",infoBg:"#dbeafe",infoBorder:"#93c5fd",infoText:"#1e40af"}}function je(e,t){const r=t?{...e,...t}:e,n=r.dark??!1,o=r.primaryColor,a=r.shell??(n?"#141414":"#ffffff");return{primaryColor:o,dark:n,fontFamily:r.fontFamily??'"Helvetica Neue", sans-serif',appName:r.appName??"Spicenet",logo:r.logo,borderRadius:r.borderRadius??"8px",shell:a,shellBorder:te(a,n?.35:.085),shellInnerBorder:te(a,n?.55:.165),card:r.card??(n?"#1e1e1e":"#f9fafb"),input:n?"#171717":"#ffffff",hover:n?"#2a2a2a":"#f3f4f6",text:r.text??(n?"#ffffff":"#111827"),textMuted:r.textMuted??(n?"#888888":"#6b7280"),inputText:n?"#e0e0e0":"#111827",inputPlaceholder:n?"#555555":"#9ca3af",border:r.border??(n?"rgba(255,255,255,0.08)":"#e5e7eb"),inputBorder:n?"rgba(255,255,255,0.10)":"#d1d5db",buttonBorder:n?"rgba(255,255,255,0.12)":"#0e0d0b",successBg:n?"#0a1f18":"#d1fae5",successBorder:n?"#1a4a3a":"#6ee7b7",successText:n?"#6ee7b7":"#065f46",warningBg:n?"#1f1a0d":"#fef3c7",warningBorder:n?"#4a3820":"#fde68a",warningText:n?"#fbbf24":"#92400e",errorBg:n?"#1f0d0d":"#fef2f2",errorBorder:n?"#4a2020":"#fecaca",errorText:n?"#f87171":"#dc2626",infoBg:n?"#0d1325":"#dbeafe",infoBorder:n?"#2a3a5a":"#93c5fd",infoText:n?"#60a5fa":"#1e40af"}}const dr=Q.mono;function De(e,t){const r=y.useContext(qe)??void 0;return y.useMemo(()=>{const n=r??{primaryColor:"#EA4B4B"},o={...e?.primaryColor?{primaryColor:e.primaryColor}:{},...e?.fontFamily?{fontFamily:e.fontFamily}:{},...t!==void 0?{dark:t}:{}},a=je(n,o),{dark:s,primaryColor:l,appName:p}=a,c=Oe(s?"dark":"light"),m={...c,colors:{...c.colors,primary:l,primaryHover:`${l}dd`},typography:{...c.typography,fontFamily:a.fontFamily}},d={shell:a.shell,shellBorder:a.shellBorder,shellInnerBorder:a.shellInnerBorder,cardBg:a.card,inputBg:a.input,hoverBg:a.hover,textPrimary:a.text,textSecondary:a.textMuted,inputText:a.inputText,inputPlaceholder:a.inputPlaceholder,cardBorder:a.border,inputBorder:a.inputBorder,buttonBorder:a.buttonBorder,successBg:a.successBg,successBorder:a.successBorder,successText:a.successText,warningBg:a.warningBg,warningBorder:a.warningBorder,warningText:a.warningText,errorBg:a.errorBg,errorBorder:a.errorBorder,errorText:a.errorText,infoBg:a.infoBg,infoBorder:a.infoBorder,infoText:a.infoText},f=e?.inputVariant?e.inputVariant==="dark":s;return{brand:r,theme:m,dark:s,appName:p,primaryColor:l,dk:d,palette:d,fontFamily:a.fontFamily,monoFont:dr,inputDark:f,cardBg:f?d.cardBg:"#E9E9E9",cardBorder:f?d.cardBorder:"#C6C6C6",textPrimary:d.textPrimary,textSecondary:d.textSecondary,subtitleText:f?d.textSecondary:"#979AA0"}},[r,e,t])}const _e="spiceflow-spinner-keyframes";function ur(){if(typeof document>"u"||document.getElementById(_e))return;const e=document.createElement("style");e.id=_e,e.textContent="@keyframes spiceflow-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}",document.head.appendChild(e)}const ue=({size:e=16,borderWidth:t=2,color:r="currentColor",style:n})=>{const o=y.useRef(!1);return y.useEffect(()=>{o.current||(ur(),o.current=!0)},[]),C.jsx("div",{role:"status","aria-label":"Loading",style:{width:`${e}px`,height:`${e}px`,border:`${t}px solid ${r}`,borderTop:`${t}px solid transparent`,borderRadius:"50%",animation:"spiceflow-spin 1s linear infinite",flexShrink:0,...n}})},_=({variant:e="primary",size:t="md",loading:r=!1,fullWidth:n=!1,disabled:o,children:a,className:s="",style:l,theme:p,styles:c,dark:m,...d})=>{const{theme:f,dark:u,palette:w}=De(c,m),i=p||f,g=m??u,[x,b]=y.useState(!1),[h,$]=y.useState(!1),k=g?w.shell:"white",S=()=>{switch(e){case"primary":return{backgroundColor:c?.button?.backgroundColor||i.colors.primary,color:c?.button?.color||"#ffffff",border:`1px solid ${c?.button?.backgroundColor||i.colors.primary}`};case"secondary":return{backgroundColor:i.colors.secondary,color:i.colors.text,border:`1px solid ${i.colors.border}`};case"success":return{backgroundColor:i.colors.success,color:i.colors.text,border:`1px solid ${i.colors.success}`};case"error":return{backgroundColor:i.colors.error,color:i.colors.text,border:`1px solid ${i.colors.error}`};case"ghost":return{backgroundColor:"transparent",color:i.colors.textSecondary,border:`1px solid ${i.colors.border}`};case"outline":{const A=c?.button?.backgroundColor||i.colors.primary,z=c?.button?.color||(g?"#ffffff":"#0e0d0b"),U=c?.button?.borderColor||(g?`${i.colors.primary}88`:"#0e0d0b"),L=o||r,q=g?"1px":"1.5px",ze=c?.button?.disabledBackgroundColor||`${A}88`,Je=c?.button?.disabledColor||z;return{height:"56px",backgroundColor:L?ze:h?`${A}cc`:x?`${A}ee`:A,color:L?Je:z,border:`${q} solid ${U}`,borderRadius:c?.button?.borderRadius||"12px",fontSize:"22px",fontWeight:500,fontFamily:Q.mono,opacity:L?.78:1,transform:L?"none":h?"scale(0.98)":x?"translateY(-2px)":"none",boxShadow:L?`3px 3px 0px ${k}, 4px 4px 0px ${U}`:h?`2px 2px 0px ${U}`:x?`5px 5px 0px ${k}, 6px 6px 0px ${U}`:`3px 3px 0px ${k}, 4px 4px 0px ${U}`,transition:"transform 150ms ease, box-shadow 150ms ease, background-color 150ms ease, border-color 150ms ease, color 150ms ease"}}default:return{}}},E=()=>{if(e==="outline")return{};switch(t){case"sm":return{padding:`${i.spacing.sm} ${i.spacing.md}`,fontSize:i.typography.fontSize.sm};case"md":return{padding:`${i.spacing.md} ${i.spacing.lg}`,fontSize:i.typography.fontSize.base};case"lg":return{padding:`${i.spacing.lg} ${i.spacing.xl}`,fontSize:i.typography.fontSize.lg};default:return{}}},N={borderRadius:c?.button?.borderRadius||i.borderRadius.md,fontWeight:c?.button?.fontWeight||i.typography.fontWeight.medium,fontFamily:c?.button?.fontFamily||i.typography.fontFamily,fontSize:c?.button?.fontSize||void 0,transition:"background-color 150ms ease, border-color 150ms ease, color 150ms ease, opacity 150ms ease",cursor:o||r?"not-allowed":"pointer",opacity:(o||r)&&e!=="outline"?.6:1,width:n?"100%":"auto",display:"flex",alignItems:"center",justifyContent:"center",gap:i.spacing.sm,...S(),...E(),...c?.button?.fontSize&&{fontSize:c.button.fontSize}},I=o||r;return C.jsxs("button",{type:d.type??"button","data-spiceflow-focus":!0,"aria-busy":r||void 0,"aria-disabled":I||void 0,style:{...N,...l},className:s,disabled:I,onMouseEnter:A=>{b(!0),d.onMouseEnter?.(A)},onMouseLeave:A=>{b(!1),$(!1),d.onMouseLeave?.(A)},onMouseDown:A=>{$(!0),d.onMouseDown?.(A)},onMouseUp:A=>{$(!1),d.onMouseUp?.(A)},...d,children:[r&&C.jsx(ue,{size:16}),a]})},fr=({onAuthSuccess:e,onAuthError:t,theme:r="light",className:n="",buttonText:o="Login with Privy",autoTrigger:a=!1})=>{const s=Oe(r),[l,p]=y.useState(!1),[c,m]=y.useState(!1),{ready:d,authenticated:f,user:u}=T.usePrivy(),{login:w}=T.useLogin(),{logout:i}=T.useLogout();y.useEffect(()=>{p(!0)},[]),y.useEffect(()=>{a&&d&&!f&&l&&!c&&(m(!0),g())},[a,d,f,l,c]),y.useEffect(()=>{if(d&&f&&u&&e){const b=u.wallet?.address||u.email?.address;b&&e(b)}},[d,f,u,e]),y.useEffect(()=>{},[d,f,t]);const g=()=>{try{w()}catch(b){const h=`Login failed: ${String(b)}`;t?t(h):console.error(h)}},x=()=>{try{i()}catch(b){const h=`Logout failed: ${String(b)}`;t?t(h):console.error(h)}};if(!l)return C.jsx("div",{className:n,children:C.jsx(_,{variant:"primary",disabled:!0,theme:s,children:o})});if(!d)return C.jsx("div",{className:n,children:C.jsx(_,{variant:"primary",disabled:!0,theme:s,children:"Loading..."})});if(f&&u){const b=u.wallet?.address||u.email?.address,h=b?Re(b):"Connected";return C.jsx("div",{className:n,children:C.jsxs(_,{variant:"primary",onClick:x,theme:s,children:["Logout (",h,")"]})})}return a?C.jsx("div",{className:n,children:C.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",padding:"20px",flexDirection:"column",gap:"12px"},children:[C.jsx(ue,{size:40,borderWidth:3,color:"#3b82f6"}),C.jsx("p",{style:{color:"#6b7280",fontSize:"14px"},children:"Opening login..."})]})}):C.jsx("div",{className:n,children:C.jsx(_,{variant:"primary",onClick:g,theme:s,children:o})})};exports.Button=_,exports.CHAIN_CONFIGS=ot,exports.EMPTY_INVENTORY=Se,exports.PrivyLogin=fr,exports.RELAYER_API_URL=Ce,exports.SpiceFlowProvider=ar,exports.SpiceFlowProviderContext=ee,exports.SpiceflowError=R,exports.Spinner=ue,exports.addressesEqual=J,exports.amountsWithinBps=Lt,exports.buildDarkPalette=lr,exports.buildLightPalette=cr,exports.calculateBpsFee=Te,exports.copyToClipboard=Vt,exports.createInitialSteps=Tt,exports.createInventoryStore=Ie,exports.describeChainSwitchError=Et,exports.duration=P,exports.easing=F,exports.ensureSmartWallet=ke,exports.ensureWalletOnChain=lt,exports.feeAmountsWithinTolerance=Fe,exports.feeBreakdownsWithinTolerance=Mt,exports.font=Q,exports.formatAssetBalance=Kt,exports.formatBalance=Pe,exports.formatDecimalInput=Ht,exports.formatRawTokenInput=qt,exports.formatUsd=Jt,exports.getAllAssetsForChain=dt,exports.getAssetDisplayChainName=Xt,exports.getAssetDisplayName=We,exports.getAssetDisplaySymbol=Ne,exports.getBatchFees=Z,exports.getChainConfig=M,exports.getChainName=V,exports.getChainsForAssets=ct,exports.getClientForChain=ye,exports.getDelegateContractAddress=st,exports.getEscrowFreeFeePreview=_t,exports.getExplorerUrl=it,exports.getRelayerErrorDetail=bt,exports.getRpcUrlForChain=H,exports.getSingleNetOutput=Dt,exports.getSupplyFeePreview=zt,exports.getTokenDecimals=ut,exports.getWalletBalanceEntries=St,exports.hasStoredSpiceFlowMode=rr,exports.isAssetAllowed=Yt,exports.isSpiceflowError=ae,exports.isUserRejection=ve,exports.parseNumericInput=Gt,exports.radius=ht,exports.relayerFetch=X,exports.relayerService=W,exports.resolveFeeToleranceBps=de,exports.resolveTheme=je,exports.sanitizeError=wt,exports.splitFeeInclusiveAmount=Ut,exports.transition=gt,exports.truncateAddress=Re,exports.useAssetDisplay=Zt,exports.useIsUserWhitelisted=Ae,exports.useProviderWallets=ne,exports.useSpiceBrand=De,exports.useWallet=Be,exports.z=pt;