@spicenet-io/spiceflow-ui 4.10.0 → 4.11.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 CHANGED
@@ -4,23 +4,24 @@ A React component library for building DeFi swap interfaces with multi-chain sup
4
4
 
5
5
  ## Version and migration
6
6
 
7
- The shared inventory/action refactor merged in PR #103. As checked September 14, 2026,
8
- `4.9.6-consistency.16` is published under `consistency`; npm `latest` remains `4.9.5`.
9
- Check the actual registry tag and client pin before adopting it. See
10
- [CONSISTENCY-MIGRATION.md](CONSISTENCY-MIGRATION.md) for migration breaks and candidate-specific evidence.
11
- A merged source change does not publish npm or upgrade consuming apps.
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
12
 
13
- The unified action controller in this source restores direct external execution for a wallet asset matching the destination token and chain. Clients keep one action definition across direct and routed execution. See [the migration guide](CONSISTENCY-MIGRATION.md#unreleased-direct-external-execution-in-the-unified-controller) for scope, gas, recovery and the unpublished release status.
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.
14
16
 
15
17
  ## Installation and provider setup
16
18
 
17
19
  Select an approved exact version, preserving the app's dependency-age and security policies:
18
20
 
19
21
  ```bash
20
- bun add --exact @spicenet-io/spiceflow-ui@4.9.6-consistency.16
22
+ bun add --exact @spicenet-io/spiceflow-ui@4.11.0
21
23
  ```
22
24
 
23
- The example is a prerelease selection, not a stable recommendation or a policy bypass.
24
25
  Reuse compatible host-owned Privy, wagmi and QueryClient providers. Read the exact
25
26
  package's peer dependencies; do not replace the wallet stack as part of a version bump.
26
27
 
@@ -54,6 +55,10 @@ host-configured RPC. Keep private credentials out of browser bundles.
54
55
 
55
56
  - Use stock components when they fit the approved UI; use `useSpiceSupply` for custom
56
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.
57
62
  - Declare the destination token by chain and address:
58
63
  `destinationToken={{ chainId: 8453, address: USDC }}`. The SDK takes the symbol and
59
64
  decimals from the chain's token registry, then from the token contract, and holds the
@@ -275,6 +280,51 @@ match on `assetPositionId`, not on symbol or address alone.
275
280
  - `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.
276
281
  - `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.
277
282
 
283
+ ### Supply fee-estimate tolerance
284
+
285
+ In `4.11.0`, `SpiceFlowProvider` defaults `feeToleranceBps` to
286
+ `4000` (40%). Clients can override it, for example:
287
+
288
+ ```tsx
289
+ <SpiceFlowProvider {...config} feeToleranceBps={100}>
290
+ {children}
291
+ </SpiceFlowProvider>
292
+ ```
293
+
294
+ `100` is 1%; `0` disables the drift allowance. Existing conservative fixed-fee
295
+ retention still applies when estimates fall. Values must be integers from `0` to
296
+ `10000`. The allowance is relative to each accepted fee component: a fee of 100
297
+ allows a fresh estimate up to 140 at the default. A zero fee cannot become positive.
298
+
299
+ This setting covers fee convergence and pre-sign validation for user-transfer
300
+ Supply plans, including stock/controlled Supply and input-mode Lock consumers.
301
+ Within tolerance, the SDK keeps the quoted fee amounts and transaction calls;
302
+ it does not add a 40% fee buffer, increase the source spend cap, or relax swap
303
+ slippage/minimum outputs. Changing the setting invalidates prepared plans.
304
+ Backend acceptance and funding checks still apply. Exact-output Pay, fee-only
305
+ funding, deposits and backend-transfer convergence retain their separate rules.
306
+
307
+ The open Supply controller retains fee construction hints for at most 30 seconds
308
+ across amount edits in the same wallet, route and configuration scope. These hints
309
+ do not enable submission: the SDK rebuilds the current calls, then checks source
310
+ gas and destination fees concurrently. User-transfer source gas uses the same
311
+ configured fee tolerance; accepted amounts and source caps stay unchanged. A hint
312
+ that no longer fits falls back to full preparation. Closing the form clears hints.
313
+ Servers advertising fee validation price the already constructed destination fee
314
+ calls; older servers retain the estimate path. Cold preparation still sizes fees
315
+ from scratch. Input route quotes debounce for 100 ms; routed fee preparation adds
316
+ no second debounce. These optimizations do not establish a latency SLA.
317
+
318
+ Fee-inclusive, user-transfer Supply also prewarms when an eligible source is
319
+ selected with an empty/zero amount. It constructs a provisional quote for at most
320
+ one token, capped by the available balance, and retains only fee hints. The amount,
321
+ fee preview and executable plan remain empty. Typing can await in-flight prewarming
322
+ before validating the real amount; closing or changing wallet/configuration
323
+ cancels it. Failed provisional preparation leaves normal quoting available.
324
+ This does not apply to direct external actions, exact-output Pay, fee-only actions
325
+ or actions with explicit funding inputs/proceeds. No signature or submission occurs
326
+ during prewarming. The time available before typing determines how much work it hides.
327
+
278
328
  ### Fee funding for fee-only actions
279
329
 
280
330
  - 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`).
@@ -1,3 +1,3 @@
1
1
  "use client";
2
2
  (function(){if(typeof document==="undefined"||document.getElementById("spiceflow-styles"))return;var s=document.createElement("style");s.id="spiceflow-styles";s.textContent="@keyframes spiceflow-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.001ms!important}}.spiceflow-focus-ring:focus-visible,[data-spiceflow-focus]:focus-visible{border-radius:4px;outline:2px solid currentColor;outline-offset:2px}@media (forced-colors:active){.spiceflow-focus-ring:focus-visible,[data-spiceflow-focus]:focus-visible{outline:2px solid Highlight}}.ibm-plex-mono-thin{font-weight:100}.ibm-plex-mono-extralight,.ibm-plex-mono-thin{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-extralight{font-weight:200}.ibm-plex-mono-light{font-weight:300}.ibm-plex-mono-light,.ibm-plex-mono-regular{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-regular{font-weight:400}.ibm-plex-mono-medium{font-weight:500}.ibm-plex-mono-medium,.ibm-plex-mono-semibold{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-semibold{font-weight:600}.ibm-plex-mono-bold{font-family:IBM Plex Mono,monospace!important;font-style:normal;font-weight:700}#privy-modal-content button{border-radius:6px;font-family:IBM Plex Mono,monospace!important;font-size:small;letter-spacing:-.5px;text-transform:uppercase}#privy-modal-content input{background:#fff;border-radius:6px;padding-top:5px!important}#privy-dialog-title{display:none!important;font-size:18px;padding-bottom:10px;padding-top:10px}.AppLogoContainer-sc-a206bd44-0{display:none}#privy-modal-content{background:var(--Neutral-0,#fafafa);border:6px solid var(--Neutral-1,#e9e9e9);box-shadow:inset 0 0 0 1px rgba(0,0,0,.15),0 330px 92px transparent,0 211px 84px rgba(0,0,0,.01),0 119px 71px rgba(0,0,0,.05),0 53px 53px rgba(0,0,0,.09),0 13px 29px rgba(0,0,0,.1);height:auto!important;padding-bottom:10px}.Subtitle-sc-ddbf22b4-4{font-size:12px;margin-top:-14px;opacity:.5}.StyledLink-sc-1c6127e5-0{background:none!important}.StyledLink-sc-c8577138-0,.login-method-button{font-family:IBM Plex Mono,monospace!important}.StyledLink-sc-c8577138-0{font-style:uppercase}#protected-by-privy{color:#000!important}";document.head.appendChild(s)})();
3
- "use strict";const r=require("./privy-BG9UqWrL.js");require("react/jsx-runtime"),require("react"),require("@spicenet-io/spiceflow-core"),require("@tanstack/react-query"),require("viem"),require("@privy-io/react-auth"),require("wagmi"),require("viem/actions"),exports.PrivyLogin=r.PrivyLogin;
3
+ "use strict";const r=require("./privy-DvCweNRl.js");require("react/jsx-runtime"),require("react"),require("@spicenet-io/spiceflow-core"),require("@tanstack/react-query"),require("viem"),require("@privy-io/react-auth"),require("wagmi"),require("viem/actions"),exports.PrivyLogin=r.PrivyLogin;
@@ -1,3 +1,3 @@
1
1
  "use client";
2
2
  (function(){if(typeof document==="undefined"||document.getElementById("spiceflow-styles"))return;var s=document.createElement("style");s.id="spiceflow-styles";s.textContent="@keyframes spiceflow-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.001ms!important}}.spiceflow-focus-ring:focus-visible,[data-spiceflow-focus]:focus-visible{border-radius:4px;outline:2px solid currentColor;outline-offset:2px}@media (forced-colors:active){.spiceflow-focus-ring:focus-visible,[data-spiceflow-focus]:focus-visible{outline:2px solid Highlight}}.ibm-plex-mono-thin{font-weight:100}.ibm-plex-mono-extralight,.ibm-plex-mono-thin{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-extralight{font-weight:200}.ibm-plex-mono-light{font-weight:300}.ibm-plex-mono-light,.ibm-plex-mono-regular{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-regular{font-weight:400}.ibm-plex-mono-medium{font-weight:500}.ibm-plex-mono-medium,.ibm-plex-mono-semibold{font-family:IBM Plex Mono,monospace!important;font-style:normal}.ibm-plex-mono-semibold{font-weight:600}.ibm-plex-mono-bold{font-family:IBM Plex Mono,monospace!important;font-style:normal;font-weight:700}#privy-modal-content button{border-radius:6px;font-family:IBM Plex Mono,monospace!important;font-size:small;letter-spacing:-.5px;text-transform:uppercase}#privy-modal-content input{background:#fff;border-radius:6px;padding-top:5px!important}#privy-dialog-title{display:none!important;font-size:18px;padding-bottom:10px;padding-top:10px}.AppLogoContainer-sc-a206bd44-0{display:none}#privy-modal-content{background:var(--Neutral-0,#fafafa);border:6px solid var(--Neutral-1,#e9e9e9);box-shadow:inset 0 0 0 1px rgba(0,0,0,.15),0 330px 92px transparent,0 211px 84px rgba(0,0,0,.01),0 119px 71px rgba(0,0,0,.05),0 53px 53px rgba(0,0,0,.09),0 13px 29px rgba(0,0,0,.1);height:auto!important;padding-bottom:10px}.Subtitle-sc-ddbf22b4-4{font-size:12px;margin-top:-14px;opacity:.5}.StyledLink-sc-1c6127e5-0{background:none!important}.StyledLink-sc-c8577138-0,.login-method-button{font-family:IBM Plex Mono,monospace!important}.StyledLink-sc-c8577138-0{font-style:uppercase}#protected-by-privy{color:#000!important}";document.head.appendChild(s)})();
3
- import{P as g}from"./privy-D-vEVQff.js";import"react/jsx-runtime";import"react";import"@spicenet-io/spiceflow-core";import"@tanstack/react-query";import"viem";import"@privy-io/react-auth";import"wagmi";import"viem/actions";export{g as PrivyLogin};
3
+ import{P as g}from"./privy-De1a_NkQ.js";import"react/jsx-runtime";import"react";import"@spicenet-io/spiceflow-core";import"@tanstack/react-query";import"viem";import"@privy-io/react-auth";import"wagmi";import"viem/actions";export{g as PrivyLogin};
@@ -14,6 +14,7 @@ export type PaymentResult = {
14
14
  error?: string;
15
15
  };
16
16
  export type ExecutorContext = {
17
+ feeToleranceBps?: number;
17
18
  selectedAsset: SelectedAsset | null;
18
19
  parsedAmount: number;
19
20
  currentPaymentAmount: string;
@@ -45,6 +45,7 @@ export declare function sizePivotSourceGasSkim(params: {
45
45
  buildSupplyChainBatches: BuildSupplyBatchesFn;
46
46
  signal?: AbortSignal;
47
47
  }): Promise<bigint>;
48
+ export declare function retainQuotedFixedFees(estimate: FeeEstimateSummary, previous: FeeEstimateSummary): FeeEstimateSummary;
48
49
  export declare function solveGrossFunding(fees: Pick<FeePreview, "grossAmount" | "gasFee" | "spicenetFee" | "bridgeFee">, requiredNetFunding: bigint): bigint;
49
50
  export declare function estimatePayFunding(args: {
50
51
  requiredNetFunding: bigint;
@@ -83,6 +84,9 @@ export declare function estimateSupplyMaxAmount(ctx: {
83
84
  feeExecutionMode: SpiceSupplyProps["feeExecutionMode"];
84
85
  }): Promise<string | null>;
85
86
  export interface FeePreviewContext {
87
+ feeToleranceBps?: number;
88
+ initialFeeEstimate?: FeeEstimateSummary;
89
+ initialSourceGasSkim?: bigint;
86
90
  liquidityVenues?: import("../../../utils/crossChainRouter/venueConfig").LiquidityVenuesConfig;
87
91
  maxSlippage?: number;
88
92
  resolveExactOutputSwap?: SpiceSupplyProps["resolveExactOutputSwap"];
@@ -43,21 +43,21 @@ export declare function useSpiceEmbeddedWithdraw(params: {
43
43
  decimals: number;
44
44
  amount: bigint;
45
45
  } | null;
46
- inputDecimals?: number | null;
47
46
  quoteKey: string | null;
48
47
  quoteExpiresAt: number | null;
49
- refreshQuote: () => void;
50
- isQuoting: boolean;
51
48
  quoteError: string | null;
49
+ isQuoting: boolean;
52
50
  estimatedInput: string | null;
53
- exactOutputSwap: import("../../..").ResolvedExactOutputSwap | null;
54
51
  quoteAmountOut: bigint | null;
52
+ sourceSwap: import("@spicenet-io/spiceflow-core").ResolvedSwap | null;
53
+ exactOutputSwap: import("../../..").ResolvedExactOutputSwap | null;
54
+ sourceExactOutputSwap: import("../../..").ResolvedExactOutputSwap | null;
55
+ inputDecimals?: number | null;
56
+ refreshQuote: () => void;
55
57
  resolvedTokenIn: Address | null;
56
58
  isDirect: boolean;
57
59
  needsSwap: boolean;
58
60
  selectedIsEquivalent: boolean;
59
- sourceSwap: import("@spicenet-io/spiceflow-core").ResolvedSwap | null;
60
- sourceExactOutputSwap: import("../../..").ResolvedExactOutputSwap | null;
61
61
  pivot: import("../../../types/supply").PivotRoute | null;
62
62
  };
63
63
  fees: {
@@ -10,4 +10,5 @@ export declare function getChainAuthorizations(params: {
10
10
  cache?: Map<string, ChainAuthorization>;
11
11
  signal?: AbortSignal;
12
12
  onSignatureRequested?: () => void;
13
+ beforeSign?: () => Promise<void>;
13
14
  }): Promise<ChainAuthorization[]>;
@@ -36,6 +36,7 @@ export interface ExecutionOptions {
36
36
  skipPreSubmitFeeEstimate?: boolean;
37
37
  submittedFeeEstimate?: FeeEstimateSummary;
38
38
  skipUserFundingPreflight?: boolean;
39
+ validateFeeCalls?: boolean;
39
40
  signal?: AbortSignal;
40
41
  }
41
42
  export declare function validateUserFundingCapacity(chainBatches: ChainBatch[], userAddress: Address, options?: {
@@ -18,20 +18,20 @@ export declare function useSpiceLock(params: UseSpiceLockParams): {
18
18
  decimals: number;
19
19
  amount: bigint;
20
20
  } | null;
21
- inputDecimals?: number | null;
22
21
  quoteKey: string | null;
23
22
  quoteExpiresAt: number | null;
24
- isQuoting: boolean;
25
23
  quoteError: string | null;
24
+ isQuoting: boolean;
26
25
  estimatedInput: string | null;
27
- exactOutputSwap: import("..").ResolvedExactOutputSwap | null;
28
26
  quoteAmountOut: bigint | null;
27
+ sourceSwap: import("@spicenet-io/spiceflow-core").ResolvedSwap | null;
28
+ exactOutputSwap: import("..").ResolvedExactOutputSwap | null;
29
+ sourceExactOutputSwap: import("..").ResolvedExactOutputSwap | null;
30
+ inputDecimals?: number | null;
29
31
  resolvedTokenIn: import("viem").Address | null;
30
32
  isDirect: boolean;
31
33
  needsSwap: boolean;
32
34
  selectedIsEquivalent: boolean;
33
- sourceSwap: import("@spicenet-io/spiceflow-core").ResolvedSwap | null;
34
- sourceExactOutputSwap: import("..").ResolvedExactOutputSwap | null;
35
35
  pivot: import("../types/supply").PivotRoute | null;
36
36
  };
37
37
  destination: {
@@ -5,7 +5,7 @@ import type { Asset, SelectedAsset } from "../types/assets";
5
5
  import type { DestinationTokenDeclaration, SpiceSupplyProps } from "../types/supply";
6
6
  import type { FeePreview } from "../types/ui";
7
7
  export interface UseSpiceSupplyParams extends Pick<SpiceSupplyProps, "sourceChains" | "supportedSourceAssets" | "buildActionCalls" | "feeExecutionMode" | "buildSpicenetBatch" | "onPayExecute" | "resolveExactOutputSwap" | "amountDirection" | "onSubmit" | "onSuccess" | "onError" | "additionalSourceTokens" | "includeExternalWalletAssets" | "includeCrossChainEmbeddedAssets" | "feeOnlyAction" | "actionInput" | "actionProceeds" | "feeFunding" | "maxSlippage" | "resolveInputSwap"> {
8
- enabled: boolean;
8
+ enabled?: boolean;
9
9
  destinationToken: DestinationTokenDeclaration | undefined;
10
10
  selectedAsset: SelectedAsset | null;
11
11
  paymentAmount?: string;
@@ -48,21 +48,21 @@ export declare function useSpiceSupply(params: UseSpiceSupplyParams): {
48
48
  decimals: number;
49
49
  amount: bigint;
50
50
  } | null;
51
- inputDecimals?: number | null;
52
51
  quoteKey: string | null;
53
52
  quoteExpiresAt: number | null;
54
- refreshQuote: () => void;
55
- isQuoting: boolean;
56
53
  quoteError: string | null;
54
+ isQuoting: boolean;
57
55
  estimatedInput: string | null;
58
- exactOutputSwap: import("..").ResolvedExactOutputSwap | null;
59
56
  quoteAmountOut: bigint | null;
57
+ sourceSwap: ResolvedSwap | null;
58
+ exactOutputSwap: import("..").ResolvedExactOutputSwap | null;
59
+ sourceExactOutputSwap: import("..").ResolvedExactOutputSwap | null;
60
+ inputDecimals?: number | null;
61
+ refreshQuote: () => void;
60
62
  resolvedTokenIn: Address | null;
61
63
  isDirect: boolean;
62
64
  needsSwap: boolean;
63
65
  selectedIsEquivalent: boolean;
64
- sourceSwap: ResolvedSwap | null;
65
- sourceExactOutputSwap: import("..").ResolvedExactOutputSwap | null;
66
66
  pivot: import("../types/supply").PivotRoute | null;
67
67
  };
68
68
  fees: {