@symmio/trading-react 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/account-layer/index.d.ts +4 -0
  3. package/dist/account-layer/index.d.ts.map +1 -1
  4. package/dist/account-layer/index.js +30 -26
  5. package/dist/account-layer/use-account-balance-info.d.ts +15 -4
  6. package/dist/account-layer/use-account-balance-info.d.ts.map +1 -1
  7. package/dist/account-layer/use-account-balance-info.js +21 -9
  8. package/dist/account-layer/use-account-balance-info.js.map +1 -1
  9. package/dist/account-layer/use-account-balance-of.d.ts +15 -4
  10. package/dist/account-layer/use-account-balance-of.d.ts.map +1 -1
  11. package/dist/account-layer/use-account-balance-of.js +20 -9
  12. package/dist/account-layer/use-account-balance-of.js.map +1 -1
  13. package/dist/account-layer/use-affiliate-state.d.ts +26 -0
  14. package/dist/account-layer/use-affiliate-state.d.ts.map +1 -0
  15. package/dist/account-layer/use-affiliate-state.js +27 -0
  16. package/dist/account-layer/use-affiliate-state.js.map +1 -0
  17. package/dist/account-layer/use-cancel-registration.d.ts +28 -0
  18. package/dist/account-layer/use-cancel-registration.d.ts.map +1 -0
  19. package/dist/account-layer/use-cancel-registration.js +38 -0
  20. package/dist/account-layer/use-cancel-registration.js.map +1 -0
  21. package/dist/account-layer/use-deposit-and-allocate.d.ts +7 -4
  22. package/dist/account-layer/use-deposit-and-allocate.d.ts.map +1 -1
  23. package/dist/account-layer/use-deposit-and-allocate.js.map +1 -1
  24. package/dist/account-layer/use-deposit.d.ts +3 -1
  25. package/dist/account-layer/use-deposit.d.ts.map +1 -1
  26. package/dist/account-layer/use-deposit.js +17 -15
  27. package/dist/account-layer/use-deposit.js.map +1 -1
  28. package/dist/account-layer/use-generated-account-manager-address.d.ts +25 -0
  29. package/dist/account-layer/use-generated-account-manager-address.d.ts.map +1 -0
  30. package/dist/account-layer/use-generated-account-manager-address.js +27 -0
  31. package/dist/account-layer/use-generated-account-manager-address.js.map +1 -0
  32. package/dist/account-layer/use-request-to-register-affiliate.d.ts +43 -0
  33. package/dist/account-layer/use-request-to-register-affiliate.d.ts.map +1 -0
  34. package/dist/account-layer/use-request-to-register-affiliate.js +38 -0
  35. package/dist/account-layer/use-request-to-register-affiliate.js.map +1 -0
  36. package/dist/estimated-price/index.d.ts +2 -0
  37. package/dist/estimated-price/index.d.ts.map +1 -0
  38. package/dist/estimated-price/use-debounced-value.d.ts +9 -0
  39. package/dist/estimated-price/use-debounced-value.d.ts.map +1 -0
  40. package/dist/estimated-price/use-debounced-value.js +18 -0
  41. package/dist/estimated-price/use-debounced-value.js.map +1 -0
  42. package/dist/estimated-price/use-estimated-price.d.ts +46 -0
  43. package/dist/estimated-price/use-estimated-price.d.ts.map +1 -0
  44. package/dist/estimated-price/use-estimated-price.js +31 -0
  45. package/dist/estimated-price/use-estimated-price.js.map +1 -0
  46. package/dist/index.d.ts +7 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +124 -118
  49. package/dist/instant-layer/use-instant-close-auto.d.ts +1 -1
  50. package/dist/instant-layer/use-instant-close-auto.js.map +1 -1
  51. package/dist/instant-layer/use-instant-close-bulk-auto.d.ts +1 -1
  52. package/dist/instant-layer/use-instant-close-bulk-auto.js.map +1 -1
  53. package/dist/instant-layer/use-instant-open-with-tpsl.d.ts.map +1 -1
  54. package/dist/instant-layer/use-instant-open-with-tpsl.js +10 -2
  55. package/dist/instant-layer/use-instant-open-with-tpsl.js.map +1 -1
  56. package/dist/margin/index.d.ts +2 -0
  57. package/dist/margin/index.d.ts.map +1 -0
  58. package/dist/margin/use-available-instant-open-margin.d.ts +51 -0
  59. package/dist/margin/use-available-instant-open-margin.d.ts.map +1 -0
  60. package/dist/margin/use-available-instant-open-margin.js +66 -0
  61. package/dist/margin/use-available-instant-open-margin.js.map +1 -0
  62. package/dist/provider/symmio-provider.d.ts +14 -5
  63. package/dist/provider/symmio-provider.d.ts.map +1 -1
  64. package/dist/provider/symmio-provider.js +2 -2
  65. package/dist/provider/symmio-provider.js.map +1 -1
  66. package/dist/quotes/use-managed-quotes.js +16 -16
  67. package/dist/quotes/use-managed-quotes.js.map +1 -1
  68. package/dist/websocket/is-settle-notification.d.ts +15 -0
  69. package/dist/websocket/is-settle-notification.d.ts.map +1 -0
  70. package/dist/websocket/is-settle-notification.js +15 -0
  71. package/dist/websocket/is-settle-notification.js.map +1 -0
  72. package/package.json +7 -8
@@ -7,10 +7,19 @@ export interface SymmioProviderProps {
7
7
  /** React subtree that may use SYMMIO SDK hooks. */
8
8
  children: ReactNode;
9
9
  /**
10
- * Per-chain Symmio overrides (addresses, subgraphs, solver) deep-merged onto
11
- * the SDK's built-in defaults, keyed by chain id. Omit to use defaults.
10
+ * Per-chain SYMMIO configuration, keyed by chain id — deep-merged onto the
11
+ * SDK's built-in defaults (addresses, subgraphs, solver, …).
12
+ *
13
+ * **Required.** Every supported chain must set a non-zero
14
+ * `addresses.affiliatesAddress` — your frontend's on-chain affiliate (your
15
+ * identity in SYMMIO on that chain), attached to every quote so the protocol
16
+ * attributes the trade to you and routes your fee share. Affiliate addresses
17
+ * are per chain (a registration on one chain is not valid on another). The
18
+ * provider throws `AFFILIATE_ADDRESS_REQUIRED` (via `createConfig`) for any
19
+ * supported chain missing it, so trades can never silently fall back to the
20
+ * built-in default affiliate and lose attribution.
12
21
  */
13
- chainOverrides?: CreateConfigParameters["chainOverrides"];
22
+ symmioConfig: CreateConfigParameters["symmioConfig"];
14
23
  /** Chain used when a hook or action omits `chainId`. Defaults to the first supported chain. */
15
24
  defaultChainId?: number;
16
25
  /**
@@ -35,7 +44,7 @@ export interface SymmioProviderProps {
35
44
  * ```tsx
36
45
  * <WagmiProvider config={wagmiConfig}>
37
46
  * <QueryClientProvider client={queryClient}>
38
- * <SymmioProvider>
47
+ * <SymmioProvider symmioConfig={{ [SymmioSupportedChainId.HYPER_EVM]: { addresses: { affiliatesAddress: "0x…" } } }}>
39
48
  * <App />
40
49
  * </SymmioProvider>
41
50
  * </QueryClientProvider>
@@ -45,5 +54,5 @@ export interface SymmioProviderProps {
45
54
  * The SDK never mounts wagmi or a `QueryClient` for the host — those (which
46
55
  * connectors, which RPC URLs, which shared `QueryClient`) belong to the host.
47
56
  */
48
- export declare function SymmioProvider({ children, chainOverrides, defaultChainId, getWalletClient: getWalletClientProp, }: SymmioProviderProps): import("react").JSX.Element;
57
+ export declare function SymmioProvider({ children, symmioConfig, defaultChainId, getWalletClient: getWalletClientProp, }: SymmioProviderProps): import("react").JSX.Element;
49
58
  //# sourceMappingURL=symmio-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"symmio-provider.d.ts","sourceRoot":"","sources":["../../src/provider/symmio-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;IAC1D,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,iBAAiB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,cAAc,EACd,cAAc,EACd,eAAe,EAAE,mBAAmB,GACrC,EAAE,mBAAmB,+BAsCrB"}
1
+ {"version":3,"file":"symmio-provider.d.ts","sourceRoot":"","sources":["../../src/provider/symmio-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,YAAY,EAAE,sBAAsB,CAAC,cAAc,CAAC,CAAC;IACrD,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,iBAAiB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,eAAe,EAAE,mBAAmB,GACrC,EAAE,mBAAmB,+BAsCrB"}
@@ -6,9 +6,9 @@ import { useConfig as i } from "wagmi";
6
6
  import { getPublicClient as a, getWalletClient as o } from "wagmi/actions";
7
7
  import { jsx as s } from "react/jsx-runtime";
8
8
  //#region src/provider/symmio-provider.tsx
9
- function c({ children: c, chainOverrides: l, defaultChainId: u, getWalletClient: d }) {
9
+ function c({ children: c, symmioConfig: l, defaultChainId: u, getWalletClient: d }) {
10
10
  let f = i(), p = r(() => n({
11
- chainOverrides: l,
11
+ symmioConfig: l,
12
12
  defaultChainId: u,
13
13
  getClient: ({ chainId: e } = {}) => {
14
14
  let n = a(f, { chainId: e });
@@ -1 +1 @@
1
- {"version":3,"file":"symmio-provider.js","names":[],"sources":["../../src/provider/symmio-provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createConfig,\n SymmError,\n type Config,\n type CreateConfigParameters,\n type GetWalletClientFn,\n} from \"@symmio/trading-core\";\nimport { useMemo, type ReactNode } from \"react\";\nimport type { PublicClient } from \"viem\";\nimport { useConfig } from \"wagmi\";\nimport { getPublicClient, getWalletClient } from \"wagmi/actions\";\nimport { SymmioConfigContext } from \"./symmio-config-context\";\n\n/**\n * Props for {@link SymmioProvider}.\n */\nexport interface SymmioProviderProps {\n /** React subtree that may use SYMMIO SDK hooks. */\n children: ReactNode;\n /**\n * Per-chain Symmio overrides (addresses, subgraphs, solver) deep-merged onto\n * the SDK's built-in defaults, keyed by chain id. Omit to use defaults.\n */\n chainOverrides?: CreateConfigParameters[\"chainOverrides\"];\n /** Chain used when a hook or action omits `chainId`. Defaults to the first supported chain. */\n defaultChainId?: number;\n /**\n * Custom wallet-client resolver. Receives `{ chainId, from? }` from the SDK\n * and returns the wallet client to sign with. Use this to plug in\n * session-key, multi-signer, or any non-wagmi flow — the resolver decides.\n *\n * When omitted, the provider falls back to wagmi's connected wallet (ignores\n * `from`).\n */\n getWalletClient?: GetWalletClientFn;\n}\n\n/**\n * Provides the SYMMIO {@link Config} to descendant hooks. The config's viem\n * clients are resolved from the host's wagmi config, so this is the only place\n * the SDK touches wagmi.\n *\n * **Mount order matters**: this reads wagmi context and (transitively) the\n * host's `@tanstack/react-query` `QueryClient`. Both must be mounted **outside**\n * `SymmioProvider`:\n *\n * ```tsx\n * <WagmiProvider config={wagmiConfig}>\n * <QueryClientProvider client={queryClient}>\n * <SymmioProvider>\n * <App />\n * </SymmioProvider>\n * </QueryClientProvider>\n * </WagmiProvider>\n * ```\n *\n * The SDK never mounts wagmi or a `QueryClient` for the host — those (which\n * connectors, which RPC URLs, which shared `QueryClient`) belong to the host.\n */\nexport function SymmioProvider({\n children,\n chainOverrides,\n defaultChainId,\n getWalletClient: getWalletClientProp,\n}: SymmioProviderProps) {\n const wagmiConfig = useConfig();\n\n const config = useMemo<Config>(\n () =>\n createConfig({\n chainOverrides,\n defaultChainId,\n getClient: ({ chainId } = {}): PublicClient => {\n const client = getPublicClient(wagmiConfig, { chainId });\n\n if (!client)\n throw new SymmError(\n \"config\",\n \"NO_PUBLIC_CLIENT\",\n `No public client available for chain ${chainId ?? \"(default)\"}.`,\n );\n return client;\n },\n getWalletClient:\n getWalletClientProp ??\n (async ({ chainId }) => {\n try {\n return await getWalletClient(wagmiConfig, { chainId });\n } catch (err) {\n throw new SymmError(\n \"config\",\n \"NO_WALLET_CONNECTED\",\n \"No connected wallet. Connect a wallet before sending transactions.\",\n { cause: err instanceof Error ? err : undefined },\n );\n }\n }),\n }),\n [chainOverrides, defaultChainId, getWalletClientProp, wagmiConfig],\n );\n\n return <SymmioConfigContext.Provider value={config}>{children}</SymmioConfigContext.Provider>;\n}\n"],"mappings":";;;;;;;;AA6DA,SAAgB,EAAe,EAC7B,aACA,mBACA,mBACA,iBAAiB,KACK;CACtB,IAAM,IAAc,EAAU,GAExB,IAAS,QAEX,EAAa;EACX;EACA;EACA,YAAY,EAAE,eAAY,CAAC,MAAoB;GAC7C,IAAM,IAAS,EAAgB,GAAa,EAAE,WAAQ,CAAC;GAEvD,IAAI,CAAC,GACH,MAAM,IAAI,EACR,UACA,oBACA,wCAAwC,KAAW,YAAY,EACjE;GACF,OAAO;EACT;EACA,iBACE,MACC,OAAO,EAAE,iBAAc;GACtB,IAAI;IACF,OAAO,MAAM,EAAgB,GAAa,EAAE,WAAQ,CAAC;GACvD,SAAS,GAAK;IACZ,MAAM,IAAI,EACR,UACA,uBACA,sEACA,EAAE,OAAO,aAAe,QAAQ,IAAM,KAAA,EAAU,CAClD;GACF;EACF;CACJ,CAAC,GACH;EAAC;EAAgB;EAAgB;EAAqB;CAAW,CACnE;CAEA,OAAO,kBAAC,EAAoB,UAArB;EAA8B,OAAO;EAAS;CAAuC,CAAA;AAC9F"}
1
+ {"version":3,"file":"symmio-provider.js","names":[],"sources":["../../src/provider/symmio-provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createConfig,\n SymmError,\n type Config,\n type CreateConfigParameters,\n type GetWalletClientFn,\n} from \"@symmio/trading-core\";\nimport { useMemo, type ReactNode } from \"react\";\nimport type { PublicClient } from \"viem\";\nimport { useConfig } from \"wagmi\";\nimport { getPublicClient, getWalletClient } from \"wagmi/actions\";\nimport { SymmioConfigContext } from \"./symmio-config-context\";\n\n/**\n * Props for {@link SymmioProvider}.\n */\nexport interface SymmioProviderProps {\n /** React subtree that may use SYMMIO SDK hooks. */\n children: ReactNode;\n /**\n * Per-chain SYMMIO configuration, keyed by chain id — deep-merged onto the\n * SDK's built-in defaults (addresses, subgraphs, solver, …).\n *\n * **Required.** Every supported chain must set a non-zero\n * `addresses.affiliatesAddress` — your frontend's on-chain affiliate (your\n * identity in SYMMIO on that chain), attached to every quote so the protocol\n * attributes the trade to you and routes your fee share. Affiliate addresses\n * are per chain (a registration on one chain is not valid on another). The\n * provider throws `AFFILIATE_ADDRESS_REQUIRED` (via `createConfig`) for any\n * supported chain missing it, so trades can never silently fall back to the\n * built-in default affiliate and lose attribution.\n */\n symmioConfig: CreateConfigParameters[\"symmioConfig\"];\n /** Chain used when a hook or action omits `chainId`. Defaults to the first supported chain. */\n defaultChainId?: number;\n /**\n * Custom wallet-client resolver. Receives `{ chainId, from? }` from the SDK\n * and returns the wallet client to sign with. Use this to plug in\n * session-key, multi-signer, or any non-wagmi flow — the resolver decides.\n *\n * When omitted, the provider falls back to wagmi's connected wallet (ignores\n * `from`).\n */\n getWalletClient?: GetWalletClientFn;\n}\n\n/**\n * Provides the SYMMIO {@link Config} to descendant hooks. The config's viem\n * clients are resolved from the host's wagmi config, so this is the only place\n * the SDK touches wagmi.\n *\n * **Mount order matters**: this reads wagmi context and (transitively) the\n * host's `@tanstack/react-query` `QueryClient`. Both must be mounted **outside**\n * `SymmioProvider`:\n *\n * ```tsx\n * <WagmiProvider config={wagmiConfig}>\n * <QueryClientProvider client={queryClient}>\n * <SymmioProvider symmioConfig={{ [SymmioSupportedChainId.HYPER_EVM]: { addresses: { affiliatesAddress: \"0x…\" } } }}>\n * <App />\n * </SymmioProvider>\n * </QueryClientProvider>\n * </WagmiProvider>\n * ```\n *\n * The SDK never mounts wagmi or a `QueryClient` for the host — those (which\n * connectors, which RPC URLs, which shared `QueryClient`) belong to the host.\n */\nexport function SymmioProvider({\n children,\n symmioConfig,\n defaultChainId,\n getWalletClient: getWalletClientProp,\n}: SymmioProviderProps) {\n const wagmiConfig = useConfig();\n\n const config = useMemo<Config>(\n () =>\n createConfig({\n symmioConfig,\n defaultChainId,\n getClient: ({ chainId } = {}): PublicClient => {\n const client = getPublicClient(wagmiConfig, { chainId });\n\n if (!client)\n throw new SymmError(\n \"config\",\n \"NO_PUBLIC_CLIENT\",\n `No public client available for chain ${chainId ?? \"(default)\"}.`,\n );\n return client;\n },\n getWalletClient:\n getWalletClientProp ??\n (async ({ chainId }) => {\n try {\n return await getWalletClient(wagmiConfig, { chainId });\n } catch (err) {\n throw new SymmError(\n \"config\",\n \"NO_WALLET_CONNECTED\",\n \"No connected wallet. Connect a wallet before sending transactions.\",\n { cause: err instanceof Error ? err : undefined },\n );\n }\n }),\n }),\n [symmioConfig, defaultChainId, getWalletClientProp, wagmiConfig],\n );\n\n return <SymmioConfigContext.Provider value={config}>{children}</SymmioConfigContext.Provider>;\n}\n"],"mappings":";;;;;;;;AAsEA,SAAgB,EAAe,EAC7B,aACA,iBACA,mBACA,iBAAiB,KACK;CACtB,IAAM,IAAc,EAAU,GAExB,IAAS,QAEX,EAAa;EACX;EACA;EACA,YAAY,EAAE,eAAY,CAAC,MAAoB;GAC7C,IAAM,IAAS,EAAgB,GAAa,EAAE,WAAQ,CAAC;GAEvD,IAAI,CAAC,GACH,MAAM,IAAI,EACR,UACA,oBACA,wCAAwC,KAAW,YAAY,EACjE;GACF,OAAO;EACT;EACA,iBACE,MACC,OAAO,EAAE,iBAAc;GACtB,IAAI;IACF,OAAO,MAAM,EAAgB,GAAa,EAAE,WAAQ,CAAC;GACvD,SAAS,GAAK;IACZ,MAAM,IAAI,EACR,UACA,uBACA,sEACA,EAAE,OAAO,aAAe,QAAQ,IAAM,KAAA,EAAU,CAClD;GACF;EACF;CACJ,CAAC,GACH;EAAC;EAAc;EAAgB;EAAqB;CAAW,CACjE;CAEA,OAAO,kBAAC,EAAoB,UAArB;EAA8B,OAAO;EAAS;CAAuC,CAAA;AAC9F"}
@@ -2,13 +2,13 @@
2
2
  import { useSymmioChainId as e } from "../provider/use-symmio-chain-id.js";
3
3
  import { useSymmioConfig as t } from "../provider/use-symmio-config.js";
4
4
  import { normalizeSymmError as n } from "../errors/normalize-symm-error.js";
5
- import { predicateMatch as r } from "../utils/predicate-match.js";
6
- import { useVirtualAccountsAddressesOfSubAccount as i } from "../account-layer/use-virtual-accounts-addresses-of-sub-account.js";
7
- import { useInstantCloses as a } from "../instant-layer/use-instant-closes.js";
8
- import { useTpSlStore as ee } from "../tpsl/tpsl-store.js";
9
- import { useInstantOpens as te } from "../instant-layer/use-instant-opens.js";
5
+ import { useNotifications as r } from "../websocket/use-notifications.js";
6
+ import { predicateMatch as i } from "../utils/predicate-match.js";
7
+ import { useVirtualAccountsAddressesOfSubAccount as a } from "../account-layer/use-virtual-accounts-addresses-of-sub-account.js";
8
+ import { useInstantCloses as ee } from "../instant-layer/use-instant-closes.js";
9
+ import { useTpSlStore as te } from "../tpsl/tpsl-store.js";
10
+ import { useInstantOpens as ne } from "../instant-layer/use-instant-opens.js";
10
11
  import { useOptimisticQuotesStore as o } from "./optimistic-quotes-store.js";
11
- import { useNotifications as ne } from "../websocket/use-notifications.js";
12
12
  import { QuoteLifecycle as s, classifyQuoteNotificationAction as re, getInstantClosesQueryKey as c, getInstantOpensQueryKey as l, getPartyAOpenPositionsQueryKey as u, getPartyAOpenPositionsQueryOptions as ie, getPartyAPendingQuotesQueryKey as ae, getPartyAPendingQuotesQueryOptions as oe, getPredictedNextVirtualAccountQueryOptions as se, getQuoteQueryKey as ce, getQuoteQueryOptions as le, isolationTypeForSide as ue, reconcileQuotes as de, shouldAccelerateOnchainReads as fe } from "@symmio/trading-core";
13
13
  import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
14
14
  import { getAddress as g } from "viem";
@@ -27,21 +27,21 @@ function v(s) {
27
27
  let P = m(void 0), F = m({
28
28
  opens: !1,
29
29
  closes: !1
30
- }), I = m(void 0), L = m([]), ke = m("closed"), R = o((e) => e.entries), Ae = o((e) => e.clearSettled), je = b && ke.current === "open", z = y && y > 0 ? y : !1, Me = I.current ? fe(I.current) : !1, B = A ? Me ? he : je ? z : z || me : !1, Ne = I.current?.quotes, Pe = Ne?.some((e) => ye.has(e.lifecycle)) ?? !1, Fe = Ne?.some((e) => be.has(e.lifecycle)) ?? !1, Ie = (e) => !je && e ? he : z, Le = A && O ? Ie(Pe) : !1, Re = A && k ? Ie(Fe) : !1, V = te({
30
+ }), I = m(void 0), L = m([]), ke = m("closed"), R = o((e) => e.entries), Ae = o((e) => e.clearSettled), je = b && ke.current === "open", z = y && y > 0 ? y : !1, Me = I.current ? fe(I.current) : !1, B = A ? Me ? he : je ? z : z || me : !1, Ne = I.current?.quotes, Pe = Ne?.some((e) => ye.has(e.lifecycle)) ?? !1, Fe = Ne?.some((e) => be.has(e.lifecycle)) ?? !1, Ie = (e) => !je && e ? he : z, Le = A && O ? Ie(Pe) : !1, Re = A && k ? Ie(Fe) : !1, V = ne({
31
31
  partyA: v,
32
32
  chainId: S,
33
33
  query: {
34
34
  enabled: A && O,
35
35
  refetchInterval: Le
36
36
  }
37
- }), H = a({
37
+ }), H = ee({
38
38
  partyA: v,
39
39
  chainId: S,
40
40
  query: {
41
41
  enabled: A && k,
42
42
  refetchInterval: Re
43
43
  }
44
- }), U = i({
44
+ }), U = a({
45
45
  subAccount: v,
46
46
  chainId: S,
47
47
  query: {
@@ -68,21 +68,21 @@ function v(s) {
68
68
  isLoading: e.some((e) => e.isLoading),
69
69
  isFetching: e.some((e) => e.isFetching)
70
70
  })
71
- }), [G, Be] = h([]), { status: K } = ne({
71
+ }), [G, Be] = h([]), { status: K } = r({
72
72
  account: v,
73
73
  chainId: S,
74
74
  enabled: A && b,
75
75
  onNotification: d((e) => {
76
76
  Be((t) => [...t, e].slice(-ge));
77
77
  let t = e.tempQuoteId, n = e.quoteId;
78
- t && n && n !== `${t}` && ee.getState().link(BigInt(t), BigInt(n));
79
- let i = re(e.lastSeenAction), a = F.current;
80
- i === "open" ? a.opens = !0 : (i === "close" || (a.opens = !0), a.closes = !0), P.current && clearTimeout(P.current), P.current = setTimeout(() => {
81
- let e = M.current, t = { configKey: N.current }, { opens: n, closes: i } = F.current;
78
+ t && n && n !== `${t}` && te.getState().link(BigInt(t), BigInt(n));
79
+ let r = re(e.lastSeenAction), a = F.current;
80
+ r === "open" ? a.opens = !0 : (r === "close" || (a.opens = !0), a.closes = !0), P.current && clearTimeout(P.current), P.current = setTimeout(() => {
81
+ let e = M.current, t = { configKey: N.current }, { opens: n, closes: r } = F.current;
82
82
  F.current = {
83
83
  opens: !1,
84
84
  closes: !1
85
- }, e.invalidateQueries({ predicate: r(u, t) }), e.invalidateQueries({ predicate: r(ae, t) }), e.invalidateQueries({ predicate: r(ce, t) }), n && e.invalidateQueries({ predicate: r(l, t) }), i && e.invalidateQueries({ predicate: r(c, t) });
85
+ }, e.invalidateQueries({ predicate: i(u, t) }), e.invalidateQueries({ predicate: i(ae, t) }), e.invalidateQueries({ predicate: i(ce, t) }), n && e.invalidateQueries({ predicate: i(l, t) }), r && e.invalidateQueries({ predicate: i(c, t) });
86
86
  }, ve);
87
87
  }, [])
88
88
  });
@@ -92,7 +92,7 @@ function v(s) {
92
92
  if (!(!b || K !== "open")) {
93
93
  if (Ve.current) {
94
94
  let e = M.current, t = { configKey: N.current };
95
- e.invalidateQueries({ predicate: r(u, t) }), e.invalidateQueries({ predicate: r(ae, t) }), e.invalidateQueries({ predicate: r(ce, t) }), e.invalidateQueries({ predicate: r(l, t) }), e.invalidateQueries({ predicate: r(c, t) });
95
+ e.invalidateQueries({ predicate: i(u, t) }), e.invalidateQueries({ predicate: i(ae, t) }), e.invalidateQueries({ predicate: i(ce, t) }), e.invalidateQueries({ predicate: i(l, t) }), e.invalidateQueries({ predicate: i(c, t) });
96
96
  }
97
97
  Ve.current = !0;
98
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-managed-quotes.js","names":[],"sources":["../../src/quotes/use-managed-quotes.ts"],"sourcesContent":["\"use client\";\n\nimport {\n classifyQuoteNotificationAction,\n getInstantClosesQueryKey,\n getInstantOpensQueryKey,\n getPartyAOpenPositionsQueryKey,\n getPartyAOpenPositionsQueryOptions,\n getPartyAPendingQuotesQueryKey,\n getPartyAPendingQuotesQueryOptions,\n getPredictedNextVirtualAccountQueryOptions,\n getQuoteQueryKey,\n getQuoteQueryOptions,\n isolationTypeForSide,\n QuoteLifecycle,\n reconcileQuotes,\n shouldAccelerateOnchainReads,\n type Notification,\n type Quote,\n type ReconcileQuotesResult,\n type SocketStatus,\n type UnifiedQuote,\n} from \"@symmio/trading-core\";\nimport { useQueries, useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getAddress, type Address } from \"viem\";\nimport { useVirtualAccountsAddressesOfSubAccount } from \"../account-layer/use-virtual-accounts-addresses-of-sub-account\";\nimport { normalizeSymmError } from \"../errors/normalize-symm-error\";\nimport type { SymmioRequestError } from \"../errors/symmio-request-error\";\nimport { useInstantCloses } from \"../instant-layer/use-instant-closes\";\nimport { useInstantOpens } from \"../instant-layer/use-instant-opens\";\nimport { useSymmioChainId } from \"../provider/use-symmio-chain-id\";\nimport { useSymmioConfig } from \"../provider/use-symmio-config\";\nimport { useTpSlStore } from \"../tpsl/tpsl-store\";\nimport { predicateMatch } from \"../utils\";\nimport { useNotifications } from \"../websocket/use-notifications\";\nimport { useOptimisticQuotesStore } from \"./optimistic-quotes-store\";\n\n/**\n * Baseline poll interval (ms) used **only as a fallback** when the live event channel\n * is unavailable (`live` is off, or the notifications socket is not `open`). With the\n * socket up the feature is events-first: there is no idle on-chain poll unless the\n * consumer opts into one via `pollingInterval`.\n */\nconst FALLBACK_POLLING_INTERVAL = 5_000;\n\n/** Accelerated quote-polling interval (ms) while a row is mid-transition. */\nconst ACCELERATED_POLLING_INTERVAL = 1_500;\n\n/**\n * Cap on the live-notification buffer fed back into the merge. Reconciliation is\n * idempotent over already-applied notifications, so only a recent window is kept\n * to bound memory and reprocessing.\n *\n * TODO: check this and packages/trading-react/src/quotes/use-managed-quotes.ts#L188-L206\n */\nconst NOTIFICATION_BUFFER_LIMIT = 100;\n\n/** Stable empty array so the `accounts` memo doesn't churn while no VAs are retained. */\nconst NO_RETAINED_VAS: readonly Address[] = [];\n\n/** Debounce (ms) for coalescing a burst of notifications into one set of read invalidations. */\nconst NOTIFICATION_INVALIDATE_DEBOUNCE = 250;\n\n/**\n * Lifecycle stages that mean an open is in flight, so the hedger instant-opens\n * feed is worth polling. Outside these stages the feed has nothing new (a\n * settled position is tracked by the on-chain reads), so polling it is wasted.\n */\nconst OPEN_INTENT_LIFECYCLES = new Set<QuoteLifecycle>([\n QuoteLifecycle.OPTIMISTIC,\n QuoteLifecycle.PRICE_FILLED,\n QuoteLifecycle.WRITE_ONCHAIN,\n]);\n\n/**\n * Lifecycle stages that mean a close is in flight, so the hedger instant-closes\n * feed is worth polling. Outside these stages there is no pending close, so\n * polling the feed (merely holding open positions) is wasted.\n */\nconst CLOSE_INTENT_LIFECYCLES = new Set<QuoteLifecycle>([QuoteLifecycle.WRITE_ONCHAIN_CLOSE, QuoteLifecycle.CLOSING]);\n\n/**\n * Which underlying quote sources {@link useManagedQuotes} should read. Every\n * source defaults to `true`; set one to `false` to skip its query/subscription\n * entirely (e.g. a screen that never opens instant trades).\n */\nexport interface ManagedQuotesSources {\n /** Read on-chain open positions via `usePartyAOpenPositions`. Default `true`. */\n openPositions?: boolean;\n /** Read on-chain pending quote ids (hydrated per-id) via `usePartyAPendingQuotes`. Default `true`. */\n pendingQuotes?: boolean;\n /** Read pending instant-opens from the hedger via `useInstantOpens`. Default `true`. */\n instantOpens?: boolean;\n /** Read pending instant-closes from the hedger via `useInstantCloses`. Default `true`. */\n instantCloses?: boolean;\n}\n\n/**\n * Parameters for {@link useManagedQuotes}.\n */\nexport interface UseManagedQuotesParameters {\n /** Sub-account (partyA) to manage quotes for. The hook is idle until this is set. */\n partyA?: Address;\n /** Target chain id. Defaults to the connected chain. */\n chainId?: number;\n /**\n * Opt-in idle polling cadence (ms). **Default off** — with `live` notifications the\n * feature is events-first: it does not poll at idle, only (a) accelerates on-chain\n * reads briefly while a row awaits on-chain confirmation (the RPC-lag retry), and\n * (b) falls back to a baseline poll when the event channel is down. Set this to make\n * it poll on a fixed cadence regardless (e.g. a live-inspector panel).\n */\n pollingInterval?: number;\n /** Subscribe to the live notifications stream. Default `true`; set `false` for poll-only. */\n live?: boolean;\n /** Master switch. When `false` every source is idle regardless of `sources`. Default `true`. */\n enabled?: boolean;\n /**\n * Union the sub-account's existing on-chain Virtual Accounts (VAs) — plus the\n * predicted VAs of its pending instant-opens and any VAs seen on the\n * notifications stream — into the on-chain reads. In lowcap each isolated\n * position lives under a VA, so this is required to see the full picture.\n * Default `true`.\n */\n includeVirtualAccounts?: boolean;\n /** Extra accounts to read quotes across (e.g. VAs already known to the consumer). */\n extraAccounts?: readonly Address[];\n /** Per-source toggles; omit to read every source. */\n sources?: ManagedQuotesSources;\n}\n\n/**\n * Value returned by {@link useManagedQuotes}.\n */\nexport interface UseManagedQuotesResult {\n /** Merged, de-duplicated, lifecycle-tagged rows, newest first. */\n quotes: UnifiedQuote[];\n /** The same rows indexed by their stable {@link UnifiedQuote.key}. */\n byKey: Record<string, UnifiedQuote>;\n /** The on-chain `partyA` accounts the quotes were read across (sub-account + VAs + predicted + extras). */\n accounts: Address[];\n /** `true` while any enabled source has not produced its first result. */\n isLoading: boolean;\n /** `true` while any enabled source is fetching (background refetch included). */\n isFetching: boolean;\n /** Live connection status of the notifications socket (`\"closed\"` when `live` is off). */\n socketStatus: SocketStatus;\n /** The first error surfaced by any source, normalized, or `null`. */\n error: SymmioRequestError | null;\n /** Force every enabled source to refetch immediately. */\n refetch: () => void;\n}\n\n/**\n * Orchestrate every quote source for one sub-account — fanned out across every\n * account that holds its positions — into a single, stable, lifecycle-tagged\n * table.\n *\n * In lowcap, isolated positions live under Virtual Accounts (VAs), so a\n * sub-account's quotes are spread across its VAs rather than the sub-account\n * itself. This resolves the full account set — the sub-account, its existing\n * VAs (`useVirtualAccountsAddressesOfSubAccount`), the predicted VAs of its\n * pending instant-opens (`getPredictedNextVirtualAccount`), VAs seen on the\n * notifications stream, and any `extraAccounts` — then reads\n * `getPartyAOpenPositions` and `getPartyAPendingQuotes` (+ per-id `getQuote`\n * hydration) across that set via `useQueries`. Off-chain hedger reads\n * (`useInstantOpens` / `useInstantCloses`) stay scoped to the sub-account.\n *\n * Every source is gated by `sources`, `enabled`, and a set `partyA`, then their\n * snapshots flow through the pure core `reconcileQuotes` (\"active quotes only\" —\n * a quote shows iff it is in a source this tick), stamping each optimistic\n * instant-open with its predicted VA via `instantOpenVaByTempId`. Live\n * notifications (via `useNotifications`, unless `live` is `false`) are applied in\n * the same merge. With `live` notifications the feature is **events-first**: it does\n * not poll at idle, only accelerating the on-chain reads briefly while a row awaits\n * on-chain confirmation (`shouldAccelerateOnchainReads`, the RPC-lag retry), and\n * falling back to a baseline poll only when the event channel is down.\n *\n * Optimistic instant-opens seeded into {@link useOptimisticQuotesStore} are\n * merged in as extra `instantOpens` so a just-submitted trade shows instantly,\n * then cleared once the same `tempQuoteId` lands on-chain.\n *\n * @example\n * ```tsx\n * const { quotes, accounts, socketStatus, isLoading } = useManagedQuotes({ partyA });\n * ```\n */\nexport function useManagedQuotes(parameters: UseManagedQuotesParameters): UseManagedQuotesResult {\n const { partyA, chainId, pollingInterval, live = true, enabled = true } = parameters;\n const config = useSymmioConfig();\n const defaultChainId = useSymmioChainId();\n const resolvedChainId = chainId ?? defaultChainId;\n const includeVirtualAccounts = parameters.includeVirtualAccounts ?? true;\n const extraAccounts = parameters.extraAccounts;\n const sources = parameters.sources;\n const wantOpenPositions = sources?.openPositions ?? true;\n const wantPendingQuotes = sources?.pendingQuotes ?? true;\n const wantInstantOpens = sources?.instantOpens ?? true;\n const wantInstantCloses = sources?.instantCloses ?? true;\n const active = enabled && Boolean(partyA);\n\n /**\n * Identity of the current account set's owner. VA retention (below) is scoped\n * to this `(chainId, partyA)` so switching sub-accounts never carries another\n * account's VAs into the read set.\n */\n const retentionOwner = active && partyA ? `${resolvedChainId}:${partyA.toLowerCase()}` : undefined;\n\n /**\n * VAs retained across the optimistic→on-chain hand-off. A predicted VA leaves\n * `predictedVas` the instant its instant-open lands on-chain (the hedger drops\n * the pending open), while `getVirtualAccountsAddressesOfSubAccount` and the\n * notification `va_address` can lag a poll behind — so without retention we'd\n * stop polling the exact VA the position just landed in, and the row would\n * vanish for a tick before reappearing as its `ONCHAIN` twin. VAs are\n * append-only on-chain, so once discovered we keep reading them while the owner\n * is unchanged. See reference-va-address-resolution in agent memory.\n */\n const [retained, setRetained] = useState<{ owner?: string; vas: Address[] }>({ vas: [] });\n const retainedVas = retained.owner === retentionOwner ? retained.vas : NO_RETAINED_VAS;\n\n /**\n * Notification-driven invalidation. Reads are kept in sync off the live\n * notifications stream — not polling — so the feature works with polling off.\n * `queryClient` and the cache scope (`configKey`) are mirrored into refs so the\n * `onNotification` callback below can stay referentially stable (a new callback\n * identity would resubscribe the socket). The debounce timer coalesces a burst\n * of notifications into one invalidation pass.\n */\n const queryClient = useQueryClient();\n const queryClientRef = useRef(queryClient);\n queryClientRef.current = queryClient;\n const configKey = config.getChainConfigKey(resolvedChainId);\n const configKeyRef = useRef(configKey);\n configKeyRef.current = configKey;\n const invalidateTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n /**\n * Which hedger feeds a debounced notification burst should invalidate. A burst\n * can mix open and close events, so the kinds are accumulated here across the\n * debounce window and read (then reset) when the timer fires — scoping the\n * invalidation to the feeds the events can actually affect.\n */\n const pendingInvalidationRef = useRef<{ opens: boolean; closes: boolean }>({ opens: false, closes: false });\n\n /**\n * Last reconciliation result, kept in a ref so it survives renders without\n * triggering one. Its only remaining job is to feed\n * {@link shouldAccelerateOnchainReads} (and the feed-intent gates) the previous\n * tick's result, since the source queries set their `refetchInterval` before this\n * render's result exists. (\"Active quotes only\" dropped the grace window, and\n * `links` are recomputed fresh on each merge.)\n */\n const previousRef = useRef<ReconcileQuotesResult | undefined>(undefined);\n\n /**\n * Rows anchored on a prior tick (a notification set their `quoteId`) but not yet\n * returned by the on-chain read — the previous merge's `pendingAnchors`. Fed back\n * into {@link reconcileQuotes} so an anchored row keeps showing while the RPC\n * catches up, instead of vanishing once the hedger drops its pending open. Held in\n * a ref so it survives renders without triggering one; the source query that\n * dropped the row drives the recompute that reads it.\n */\n const retainedAnchorsRef = useRef<UnifiedQuote[]>([]);\n\n /**\n * Last tick's socket status, read one render behind (like `previousRef`) to decide\n * whether the live event channel is up. Updated right after `useNotifications` below.\n * When the channel is live the feature is events-first (no idle poll); when it is\n * down we fall back to polling so state still advances without notifications.\n */\n const socketStatusRef = useRef<SocketStatus>(\"closed\");\n\n const optimisticEntries = useOptimisticQuotesStore((state) => state.entries);\n const clearSettled = useOptimisticQuotesStore((state) => state.clearSettled);\n\n /** Live event channel = subscribed (`live`) and the socket was `open` last render. */\n const eventChannelLive = live && socketStatusRef.current === \"open\";\n /** Consumer's opt-in idle cadence (off by default). */\n const idleInterval = pollingInterval && pollingInterval > 0 ? pollingInterval : false;\n\n /**\n * On-chain reads accelerate to {@link ACCELERATED_POLLING_INTERVAL} **only** while a\n * row awaits on-chain confirmation (`WRITE_ONCHAIN` / `WRITE_ONCHAIN_CLOSE` /\n * `CLOSING`) — the RPC-lag retry. Otherwise: no idle poll when the event channel is\n * live (events drive updates) unless the consumer opted into `idleInterval`; a\n * baseline fallback when the channel is down so state still progresses. Read from the\n * previous result because the queries set their interval before this render's result\n * exists (an on-chain poll / notification re-render advances it a tick later).\n */\n const accelerateOnchain = previousRef.current ? shouldAccelerateOnchainReads(previousRef.current) : false;\n const onchainIdleInterval = eventChannelLive ? idleInterval : idleInterval || FALLBACK_POLLING_INTERVAL;\n const refetchInterval = active ? (accelerateOnchain ? ACCELERATED_POLLING_INTERVAL : onchainIdleInterval) : false;\n\n /**\n * Hedger feeds are event-driven while the channel is live: the open/close mutation\n * invalidates the matching feed once and the notifications advance the stage, so they\n * do not poll (unless the consumer opted into `idleInterval`). When the channel is\n * down they poll while the matching flow is in flight, to track it without\n * notifications. `hasOpenIntent` / `hasCloseIntent` come from the previous tick.\n */\n const previousQuotes = previousRef.current?.quotes;\n const hasOpenIntent = previousQuotes?.some((quote) => OPEN_INTENT_LIFECYCLES.has(quote.lifecycle)) ?? false;\n const hasCloseIntent = previousQuotes?.some((quote) => CLOSE_INTENT_LIFECYCLES.has(quote.lifecycle)) ?? false;\n const feedInterval = (hasIntent: boolean) =>\n !eventChannelLive && hasIntent ? ACCELERATED_POLLING_INTERVAL : idleInterval;\n const instantOpensInterval = active && wantInstantOpens ? feedInterval(hasOpenIntent) : false;\n const instantClosesInterval = active && wantInstantCloses ? feedInterval(hasCloseIntent) : false;\n\n /**\n * Off-chain hedger reads — always scoped to the sub-account, never the VAs. Kept\n * `enabled` so a mutation's `onSuccess` invalidation can fetch them on demand, but\n * polled only while the matching flow is in flight (see the interval gates above).\n */\n const instantOpens = useInstantOpens({\n partyA: partyA as Address,\n chainId: resolvedChainId,\n query: { enabled: active && wantInstantOpens, refetchInterval: instantOpensInterval },\n });\n\n const instantCloses = useInstantCloses({\n partyA: partyA as Address,\n chainId: resolvedChainId,\n query: { enabled: active && wantInstantCloses, refetchInterval: instantClosesInterval },\n });\n\n /** The sub-account's existing on-chain VAs. Empty (harmless) when `partyA` is itself a VA. */\n const virtualAccounts = useVirtualAccountsAddressesOfSubAccount({\n subAccount: partyA,\n chainId: resolvedChainId,\n query: { enabled: active && includeVirtualAccounts, refetchInterval },\n });\n\n /**\n * Brand-new-VA bridge: a position can open in a market the sub-account has\n * never traded, landing in a freshly created VA that is not yet in\n * `getVirtualAccountsAddressesOfSubAccount`. The predicted VA below covers it\n * while the instant-open is pending; once it lands on-chain the hedger drops the\n * open and the prediction with it, so VA retention (`retained`, above) keeps the\n * VA in the poll set across the hand-off — the row transitions to `ONCHAIN` in\n * place instead of flickering out and back.\n */\n\n /**\n * Predict the VA each pending instant-open will land in. The set of opens is\n * dynamic, but `useQueries` over a dynamic array is allowed — the hook count\n * stays one regardless of length.\n */\n const pendingOpens = wantInstantOpens ? (instantOpens.data ?? []) : [];\n const predictedVas = useQueries({\n queries: pendingOpens.map((open) => ({\n ...getPredictedNextVirtualAccountQueryOptions(config, {\n subAccount: partyA,\n chainId: resolvedChainId,\n isolationType: isolationTypeForSide(open.positionType),\n symbolId: BigInt(open.marketId),\n query: { enabled: active && includeVirtualAccounts, refetchInterval },\n }),\n })),\n combine: (results) => ({\n byTempId: pendingOpens.reduce<Record<number, Address>>((map, open, index) => {\n const predicted = results[index]?.data;\n if (predicted) map[open.tempQuoteId] = getAddress(predicted);\n return map;\n }, {}),\n addresses: results.map((result) => result.data).filter((address): address is Address => Boolean(address)),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n }),\n });\n\n const [notifications, setNotifications] = useState<Notification[]>([]);\n const { status: socketStatus } = useNotifications({\n account: partyA,\n chainId: resolvedChainId,\n enabled: active && live,\n onNotification: useCallback((notification: Notification) => {\n setNotifications((prev) => [...prev, notification].slice(-NOTIFICATION_BUFFER_LIMIT));\n // Solver notification carries both `tempQuoteId` and the on-chain\n // `quoteId` on the frame that anchors a pre-chain quote. Link the two\n // in the TP/SL store so a record that was written under the temp id\n // (via `useSetQuoteTpSl` post-instant-open) is instantly reachable via\n // the on-chain id once the anchor lands — no re-fetch needed.\n const tempId = notification.tempQuoteId;\n const onchainStr = notification.quoteId;\n if (tempId && onchainStr && onchainStr !== `${tempId}`) {\n useTpSlStore.getState().link(BigInt(tempId), BigInt(onchainStr));\n }\n /**\n * Refetch the authoritative reads off the live event so both tables stay in\n * sync without polling. The on-chain reads always refresh (any lifecycle event\n * can change them); the hedger feeds refresh only for the matching flow, so an\n * open event never re-fetches the closes feed and vice versa. Kinds are\n * accumulated across the debounce window because a burst can mix open and close\n * events. Scoped to this chain (`configKey`); React Query dedups concurrent\n * refetches of the same query.\n */\n const kind = classifyQuoteNotificationAction(notification.lastSeenAction);\n const pending = pendingInvalidationRef.current;\n if (kind === \"open\") pending.opens = true;\n else if (kind === \"close\") pending.closes = true;\n else {\n pending.opens = true;\n pending.closes = true;\n }\n if (invalidateTimerRef.current) clearTimeout(invalidateTimerRef.current);\n invalidateTimerRef.current = setTimeout(() => {\n const client = queryClientRef.current;\n const scope = { configKey: configKeyRef.current };\n const { opens, closes } = pendingInvalidationRef.current;\n pendingInvalidationRef.current = { opens: false, closes: false };\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAOpenPositionsQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAPendingQuotesQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getQuoteQueryKey, scope) });\n if (opens) void client.invalidateQueries({ predicate: predicateMatch(getInstantOpensQueryKey, scope) });\n if (closes) void client.invalidateQueries({ predicate: predicateMatch(getInstantClosesQueryKey, scope) });\n }, NOTIFICATION_INVALIDATE_DEBOUNCE);\n }, []),\n });\n\n /** Mirror the live socket status into the ref the interval gating reads next render. */\n socketStatusRef.current = socketStatus;\n\n /**\n * One-shot re-sync when the notifications socket **reconnects**. The stream does not\n * replay messages missed while it was down, so on a re-`open` we invalidate the reads\n * once to catch up — this replaces a continuous fallback poll. The first connect is\n * skipped (the queries already fetched on mount).\n */\n const hasBeenOpenRef = useRef(false);\n useEffect(() => {\n if (!live || socketStatus !== \"open\") return;\n if (hasBeenOpenRef.current) {\n const client = queryClientRef.current;\n const scope = { configKey: configKeyRef.current };\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAOpenPositionsQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAPendingQuotesQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getQuoteQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getInstantOpensQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getInstantClosesQueryKey, scope) });\n }\n hasBeenOpenRef.current = true;\n }, [socketStatus, live]);\n\n /** Clear the pending invalidation timer on unmount. */\n useEffect(() => {\n return () => {\n if (invalidateTimerRef.current) clearTimeout(invalidateTimerRef.current);\n };\n }, []);\n\n /** Distinct VA addresses seen on the notifications stream. */\n const notificationVas = useMemo<Address[]>(() => {\n const seen = new Set<string>();\n const result: Address[] = [];\n for (const notification of notifications) {\n if (!notification.vaAddress) continue;\n const checksummed = getAddress(notification.vaAddress);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) continue;\n seen.add(lower);\n result.push(checksummed);\n }\n return result;\n }, [notifications]);\n\n /**\n * The deduplicated (case-insensitive), checksummed set of on-chain accounts to\n * read quotes across — the sub-account, its VAs, the predicted VAs, the\n * notification VAs, and any extras. Order: sub-account first, then the rest.\n */\n const accounts = useMemo<Address[]>(() => {\n if (!active || !partyA) return [];\n const seen = new Set<string>();\n const result: Address[] = [];\n const add = (address: Address) => {\n const checksummed = getAddress(address);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) return;\n seen.add(lower);\n result.push(checksummed);\n };\n add(partyA);\n if (includeVirtualAccounts) {\n for (const virtualAccount of virtualAccounts.data ?? []) add(virtualAccount);\n for (const predicted of predictedVas.addresses) add(predicted);\n for (const notificationVa of notificationVas) add(notificationVa);\n for (const retainedVa of retainedVas) add(retainedVa);\n }\n for (const extra of extraAccounts ?? []) add(extra);\n return result;\n }, [\n active,\n partyA,\n includeVirtualAccounts,\n virtualAccounts.data,\n predictedVas.addresses,\n notificationVas,\n retainedVas,\n extraAccounts,\n ]);\n\n /**\n * Accumulate every VA we discover (list + predicted + notification) into the\n * retained set so none ever leaves the poll set mid-hand-off. Monotonic per\n * owner; resets when the owner `(chainId, partyA)` changes. Setting state only\n * when something new appears keeps this from looping.\n */\n useEffect(() => {\n if (!retentionOwner) return;\n const discovered = [...(virtualAccounts.data ?? []), ...predictedVas.addresses, ...notificationVas];\n setRetained((prev) => {\n const carryOver = prev.owner === retentionOwner ? prev.vas : [];\n const seen = new Set(carryOver.map((address) => address.toLowerCase()));\n const next = [...carryOver];\n let changed = prev.owner !== retentionOwner;\n for (const va of discovered) {\n const checksummed = getAddress(va);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) continue;\n seen.add(lower);\n next.push(checksummed);\n changed = true;\n }\n return changed ? { owner: retentionOwner, vas: next } : prev;\n });\n }, [retentionOwner, virtualAccounts.data, predictedVas.addresses, notificationVas]);\n\n /** On-chain open positions, fanned out across every account, flattened to one `Quote[]`. */\n const openPositions = useQueries({\n queries: accounts.map((account) => {\n const options = getPartyAOpenPositionsQueryOptions(config, {\n partyA: account,\n chainId: resolvedChainId,\n query: { enabled: active && wantOpenPositions, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n quotes: results.flatMap((result) => result.data ?? []),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n refetch: () => results.forEach((result) => result.refetch()),\n }),\n });\n /** Pending quote ids, fanned out across every account, then hydrated per id. */\n const pendingQuoteIds = useQueries({\n queries: accounts.map((account) => {\n const options = getPartyAPendingQuotesQueryOptions(config, {\n partyA: account,\n chainId: resolvedChainId,\n query: { enabled: active && wantPendingQuotes, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n ids: results.flatMap((result) => result.data ?? []),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n refetch: () => results.forEach((result) => result.refetch()),\n }),\n });\n\n const hydratedPending = useQueries({\n queries: pendingQuoteIds.ids.map((quoteId) => {\n const options = getQuoteQueryOptions(config, {\n quoteId,\n chainId: resolvedChainId,\n query: { enabled: active && wantPendingQuotes, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n quotes: results.map((result) => result.data).filter((quote): quote is Quote => Boolean(quote)),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n }),\n });\n\n const result = useMemo<ReconcileQuotesResult>(() => {\n if (!active || !partyA) {\n previousRef.current = undefined;\n retainedAnchorsRef.current = [];\n return { quotes: [], links: {}, pendingAnchors: [] };\n }\n const seededInstantOpens = [...(instantOpens.data ?? []), ...Object.values(optimisticEntries)];\n const next = reconcileQuotes({\n partyA,\n onchainPositions: wantOpenPositions ? openPositions.quotes : [],\n onchainPendingQuotes: wantPendingQuotes ? hydratedPending.quotes : [],\n instantOpens: wantInstantOpens ? seededInstantOpens : [],\n instantCloses: wantInstantCloses ? (instantCloses.data ?? []) : [],\n instantOpenVaByTempId: predictedVas.byTempId,\n notifications,\n retainedAnchors: retainedAnchorsRef.current,\n });\n previousRef.current = next;\n retainedAnchorsRef.current = next.pendingAnchors;\n return next;\n }, [\n active,\n partyA,\n openPositions.quotes,\n hydratedPending.quotes,\n instantOpens.data,\n instantCloses.data,\n predictedVas.byTempId,\n optimisticEntries,\n notifications,\n wantOpenPositions,\n wantPendingQuotes,\n wantInstantOpens,\n wantInstantCloses,\n ]);\n\n const byKey = useMemo<Record<string, UnifiedQuote>>(() => {\n const map: Record<string, UnifiedQuote> = {};\n for (const quote of result.quotes) map[quote.key] = quote;\n return map;\n }, [result.quotes]);\n\n /**\n * Drop optimistic seeds once their `tempQuoteId` has been **confirmed by the\n * on-chain read** — the linked row carries the polled struct (`raw.onchain`).\n * Clearing on the notification link alone would drop the seed while the row is\n * still `WRITE_ONCHAIN`, before any source but the notification holds it, so the\n * row would flicker out until the RPC catches up.\n */\n useEffect(() => {\n const settled = Object.keys(optimisticEntries)\n .map(Number)\n .filter((tempQuoteId) => {\n const key = result.links[tempQuoteId];\n return key !== undefined && byKey[key]?.raw.onchain !== undefined;\n });\n if (settled.length > 0) clearSettled(settled);\n }, [result, byKey, optimisticEntries, clearSettled]);\n\n const isLoading =\n (active && includeVirtualAccounts && virtualAccounts.isLoading) ||\n (active && includeVirtualAccounts && predictedVas.isLoading) ||\n (active && wantOpenPositions && openPositions.isLoading) ||\n (active && wantPendingQuotes && (pendingQuoteIds.isLoading || hydratedPending.isLoading)) ||\n (active && wantInstantOpens && instantOpens.isLoading) ||\n (active && wantInstantCloses && instantCloses.isLoading);\n\n const isFetching =\n (includeVirtualAccounts && (virtualAccounts.isFetching || predictedVas.isFetching)) ||\n (wantOpenPositions && openPositions.isFetching) ||\n (wantPendingQuotes && (pendingQuoteIds.isFetching || hydratedPending.isFetching)) ||\n (wantInstantOpens && instantOpens.isFetching) ||\n (wantInstantCloses && instantCloses.isFetching);\n\n const error =\n openPositions.error ??\n pendingQuoteIds.error ??\n hydratedPending.error ??\n virtualAccounts.error ??\n instantOpens.error ??\n instantCloses.error ??\n null;\n\n /**\n * `openPositions` / `pendingQuoteIds` are `useQueries` fan-outs (no single\n * query handle), so their combined results expose a `refetch` that fires every\n * per-account query. Refetching the ids re-drives the per-id hydration.\n */\n const refetchOpenPositions = openPositions.refetch;\n const refetchPendingQuoteIds = pendingQuoteIds.refetch;\n const refetch = useCallback(() => {\n if (includeVirtualAccounts) virtualAccounts.refetch();\n if (wantOpenPositions) refetchOpenPositions();\n if (wantPendingQuotes) refetchPendingQuoteIds();\n if (wantInstantOpens) instantOpens.refetch();\n if (wantInstantCloses) instantCloses.refetch();\n }, [\n includeVirtualAccounts,\n wantOpenPositions,\n wantPendingQuotes,\n wantInstantOpens,\n wantInstantCloses,\n virtualAccounts,\n refetchOpenPositions,\n refetchPendingQuoteIds,\n instantOpens,\n instantCloses,\n ]);\n\n return {\n quotes: result.quotes,\n byKey,\n accounts,\n isLoading: Boolean(isLoading),\n isFetching: Boolean(isFetching),\n socketStatus: live ? socketStatus : \"closed\",\n error,\n refetch,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-managed-quotes.js","names":[],"sources":["../../src/quotes/use-managed-quotes.ts"],"sourcesContent":["\"use client\";\n\nimport {\n classifyQuoteNotificationAction,\n getInstantClosesQueryKey,\n getInstantOpensQueryKey,\n getPartyAOpenPositionsQueryKey,\n getPartyAOpenPositionsQueryOptions,\n getPartyAPendingQuotesQueryKey,\n getPartyAPendingQuotesQueryOptions,\n getPredictedNextVirtualAccountQueryOptions,\n getQuoteQueryKey,\n getQuoteQueryOptions,\n isolationTypeForSide,\n QuoteLifecycle,\n reconcileQuotes,\n shouldAccelerateOnchainReads,\n type Notification,\n type Quote,\n type ReconcileQuotesResult,\n type SocketStatus,\n type UnifiedQuote,\n} from \"@symmio/trading-core\";\nimport { useQueries, useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getAddress, type Address } from \"viem\";\nimport { useVirtualAccountsAddressesOfSubAccount } from \"../account-layer/use-virtual-accounts-addresses-of-sub-account\";\nimport { normalizeSymmError } from \"../errors/normalize-symm-error\";\nimport type { SymmioRequestError } from \"../errors/symmio-request-error\";\nimport { useInstantCloses } from \"../instant-layer/use-instant-closes\";\nimport { useInstantOpens } from \"../instant-layer/use-instant-opens\";\nimport { useSymmioChainId } from \"../provider/use-symmio-chain-id\";\nimport { useSymmioConfig } from \"../provider/use-symmio-config\";\nimport { useTpSlStore } from \"../tpsl/tpsl-store\";\nimport { predicateMatch } from \"../utils\";\nimport { useNotifications } from \"../websocket/use-notifications\";\nimport { useOptimisticQuotesStore } from \"./optimistic-quotes-store\";\n\n/**\n * Baseline poll interval (ms) used **only as a fallback** when the live event channel\n * is unavailable (`live` is off, or the notifications socket is not `open`). With the\n * socket up the feature is events-first: there is no idle on-chain poll unless the\n * consumer opts into one via `pollingInterval`.\n */\nconst FALLBACK_POLLING_INTERVAL = 5_000;\n\n/** Accelerated quote-polling interval (ms) while a row is mid-transition. */\nconst ACCELERATED_POLLING_INTERVAL = 1_500;\n\n/**\n * Cap on the live-notification buffer fed back into the merge. Reconciliation is\n * idempotent over already-applied notifications, so only a recent window is kept\n * to bound memory and reprocessing.\n *\n * TODO: check this and packages/trading-react/src/quotes/use-managed-quotes.ts#L188-L206\n */\nconst NOTIFICATION_BUFFER_LIMIT = 100;\n\n/** Stable empty array so the `accounts` memo doesn't churn while no VAs are retained. */\nconst NO_RETAINED_VAS: readonly Address[] = [];\n\n/** Debounce (ms) for coalescing a burst of notifications into one set of read invalidations. */\nconst NOTIFICATION_INVALIDATE_DEBOUNCE = 250;\n\n/**\n * Lifecycle stages that mean an open is in flight, so the hedger instant-opens\n * feed is worth polling. Outside these stages the feed has nothing new (a\n * settled position is tracked by the on-chain reads), so polling it is wasted.\n */\nconst OPEN_INTENT_LIFECYCLES = new Set<QuoteLifecycle>([\n QuoteLifecycle.OPTIMISTIC,\n QuoteLifecycle.PRICE_FILLED,\n QuoteLifecycle.WRITE_ONCHAIN,\n]);\n\n/**\n * Lifecycle stages that mean a close is in flight, so the hedger instant-closes\n * feed is worth polling. Outside these stages there is no pending close, so\n * polling the feed (merely holding open positions) is wasted.\n */\nconst CLOSE_INTENT_LIFECYCLES = new Set<QuoteLifecycle>([QuoteLifecycle.WRITE_ONCHAIN_CLOSE, QuoteLifecycle.CLOSING]);\n\n/**\n * Which underlying quote sources {@link useManagedQuotes} should read. Every\n * source defaults to `true`; set one to `false` to skip its query/subscription\n * entirely (e.g. a screen that never opens instant trades).\n */\nexport interface ManagedQuotesSources {\n /** Read on-chain open positions via `usePartyAOpenPositions`. Default `true`. */\n openPositions?: boolean;\n /** Read on-chain pending quote ids (hydrated per-id) via `usePartyAPendingQuotes`. Default `true`. */\n pendingQuotes?: boolean;\n /** Read pending instant-opens from the hedger via `useInstantOpens`. Default `true`. */\n instantOpens?: boolean;\n /** Read pending instant-closes from the hedger via `useInstantCloses`. Default `true`. */\n instantCloses?: boolean;\n}\n\n/**\n * Parameters for {@link useManagedQuotes}.\n */\nexport interface UseManagedQuotesParameters {\n /** Sub-account (partyA) to manage quotes for. The hook is idle until this is set. */\n partyA?: Address;\n /** Target chain id. Defaults to the connected chain. */\n chainId?: number;\n /**\n * Opt-in idle polling cadence (ms). **Default off** — with `live` notifications the\n * feature is events-first: it does not poll at idle, only (a) accelerates on-chain\n * reads briefly while a row awaits on-chain confirmation (the RPC-lag retry), and\n * (b) falls back to a baseline poll when the event channel is down. Set this to make\n * it poll on a fixed cadence regardless (e.g. a live-inspector panel).\n */\n pollingInterval?: number;\n /** Subscribe to the live notifications stream. Default `true`; set `false` for poll-only. */\n live?: boolean;\n /** Master switch. When `false` every source is idle regardless of `sources`. Default `true`. */\n enabled?: boolean;\n /**\n * Union the sub-account's existing on-chain Virtual Accounts (VAs) — plus the\n * predicted VAs of its pending instant-opens and any VAs seen on the\n * notifications stream — into the on-chain reads. In lowcap each isolated\n * position lives under a VA, so this is required to see the full picture.\n * Default `true`.\n */\n includeVirtualAccounts?: boolean;\n /** Extra accounts to read quotes across (e.g. VAs already known to the consumer). */\n extraAccounts?: readonly Address[];\n /** Per-source toggles; omit to read every source. */\n sources?: ManagedQuotesSources;\n}\n\n/**\n * Value returned by {@link useManagedQuotes}.\n */\nexport interface UseManagedQuotesResult {\n /** Merged, de-duplicated, lifecycle-tagged rows, newest first. */\n quotes: UnifiedQuote[];\n /** The same rows indexed by their stable {@link UnifiedQuote.key}. */\n byKey: Record<string, UnifiedQuote>;\n /** The on-chain `partyA` accounts the quotes were read across (sub-account + VAs + predicted + extras). */\n accounts: Address[];\n /** `true` while any enabled source has not produced its first result. */\n isLoading: boolean;\n /** `true` while any enabled source is fetching (background refetch included). */\n isFetching: boolean;\n /** Live connection status of the notifications socket (`\"closed\"` when `live` is off). */\n socketStatus: SocketStatus;\n /** The first error surfaced by any source, normalized, or `null`. */\n error: SymmioRequestError | null;\n /** Force every enabled source to refetch immediately. */\n refetch: () => void;\n}\n\n/**\n * Orchestrate every quote source for one sub-account — fanned out across every\n * account that holds its positions — into a single, stable, lifecycle-tagged\n * table.\n *\n * In lowcap, isolated positions live under Virtual Accounts (VAs), so a\n * sub-account's quotes are spread across its VAs rather than the sub-account\n * itself. This resolves the full account set — the sub-account, its existing\n * VAs (`useVirtualAccountsAddressesOfSubAccount`), the predicted VAs of its\n * pending instant-opens (`getPredictedNextVirtualAccount`), VAs seen on the\n * notifications stream, and any `extraAccounts` — then reads\n * `getPartyAOpenPositions` and `getPartyAPendingQuotes` (+ per-id `getQuote`\n * hydration) across that set via `useQueries`. Off-chain hedger reads\n * (`useInstantOpens` / `useInstantCloses`) stay scoped to the sub-account.\n *\n * Every source is gated by `sources`, `enabled`, and a set `partyA`, then their\n * snapshots flow through the pure core `reconcileQuotes` (\"active quotes only\" —\n * a quote shows iff it is in a source this tick), stamping each optimistic\n * instant-open with its predicted VA via `instantOpenVaByTempId`. Live\n * notifications (via `useNotifications`, unless `live` is `false`) are applied in\n * the same merge. With `live` notifications the feature is **events-first**: it does\n * not poll at idle, only accelerating the on-chain reads briefly while a row awaits\n * on-chain confirmation (`shouldAccelerateOnchainReads`, the RPC-lag retry), and\n * falling back to a baseline poll only when the event channel is down.\n *\n * Optimistic instant-opens seeded into {@link useOptimisticQuotesStore} are\n * merged in as extra `instantOpens` so a just-submitted trade shows instantly,\n * then cleared once the same `tempQuoteId` lands on-chain.\n *\n * @example\n * ```tsx\n * const { quotes, accounts, socketStatus, isLoading } = useManagedQuotes({ partyA });\n * ```\n */\nexport function useManagedQuotes(parameters: UseManagedQuotesParameters): UseManagedQuotesResult {\n const { partyA, chainId, pollingInterval, live = true, enabled = true } = parameters;\n const config = useSymmioConfig();\n const defaultChainId = useSymmioChainId();\n const resolvedChainId = chainId ?? defaultChainId;\n const includeVirtualAccounts = parameters.includeVirtualAccounts ?? true;\n const extraAccounts = parameters.extraAccounts;\n const sources = parameters.sources;\n const wantOpenPositions = sources?.openPositions ?? true;\n const wantPendingQuotes = sources?.pendingQuotes ?? true;\n const wantInstantOpens = sources?.instantOpens ?? true;\n const wantInstantCloses = sources?.instantCloses ?? true;\n const active = enabled && Boolean(partyA);\n\n /**\n * Identity of the current account set's owner. VA retention (below) is scoped\n * to this `(chainId, partyA)` so switching sub-accounts never carries another\n * account's VAs into the read set.\n */\n const retentionOwner = active && partyA ? `${resolvedChainId}:${partyA.toLowerCase()}` : undefined;\n\n /**\n * VAs retained across the optimistic→on-chain hand-off. A predicted VA leaves\n * `predictedVas` the instant its instant-open lands on-chain (the hedger drops\n * the pending open), while `getVirtualAccountsAddressesOfSubAccount` and the\n * notification `va_address` can lag a poll behind — so without retention we'd\n * stop polling the exact VA the position just landed in, and the row would\n * vanish for a tick before reappearing as its `ONCHAIN` twin. VAs are\n * append-only on-chain, so once discovered we keep reading them while the owner\n * is unchanged. See reference-va-address-resolution in agent memory.\n */\n const [retained, setRetained] = useState<{ owner?: string; vas: Address[] }>({ vas: [] });\n const retainedVas = retained.owner === retentionOwner ? retained.vas : NO_RETAINED_VAS;\n\n /**\n * Notification-driven invalidation. Reads are kept in sync off the live\n * notifications stream — not polling — so the feature works with polling off.\n * `queryClient` and the cache scope (`configKey`) are mirrored into refs so the\n * `onNotification` callback below can stay referentially stable (a new callback\n * identity would resubscribe the socket). The debounce timer coalesces a burst\n * of notifications into one invalidation pass.\n */\n const queryClient = useQueryClient();\n const queryClientRef = useRef(queryClient);\n queryClientRef.current = queryClient;\n const configKey = config.getChainConfigKey(resolvedChainId);\n const configKeyRef = useRef(configKey);\n configKeyRef.current = configKey;\n const invalidateTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n /**\n * Which hedger feeds a debounced notification burst should invalidate. A burst\n * can mix open and close events, so the kinds are accumulated here across the\n * debounce window and read (then reset) when the timer fires — scoping the\n * invalidation to the feeds the events can actually affect.\n */\n const pendingInvalidationRef = useRef<{ opens: boolean; closes: boolean }>({ opens: false, closes: false });\n\n /**\n * Last reconciliation result, kept in a ref so it survives renders without\n * triggering one. Its only remaining job is to feed\n * {@link shouldAccelerateOnchainReads} (and the feed-intent gates) the previous\n * tick's result, since the source queries set their `refetchInterval` before this\n * render's result exists. (\"Active quotes only\" dropped the grace window, and\n * `links` are recomputed fresh on each merge.)\n */\n const previousRef = useRef<ReconcileQuotesResult | undefined>(undefined);\n\n /**\n * Rows anchored on a prior tick (a notification set their `quoteId`) but not yet\n * returned by the on-chain read — the previous merge's `pendingAnchors`. Fed back\n * into {@link reconcileQuotes} so an anchored row keeps showing while the RPC\n * catches up, instead of vanishing once the hedger drops its pending open. Held in\n * a ref so it survives renders without triggering one; the source query that\n * dropped the row drives the recompute that reads it.\n */\n const retainedAnchorsRef = useRef<UnifiedQuote[]>([]);\n\n /**\n * Last tick's socket status, read one render behind (like `previousRef`) to decide\n * whether the live event channel is up. Updated right after `useNotifications` below.\n * When the channel is live the feature is events-first (no idle poll); when it is\n * down we fall back to polling so state still advances without notifications.\n */\n const socketStatusRef = useRef<SocketStatus>(\"closed\");\n\n const optimisticEntries = useOptimisticQuotesStore((state) => state.entries);\n const clearSettled = useOptimisticQuotesStore((state) => state.clearSettled);\n\n /** Live event channel = subscribed (`live`) and the socket was `open` last render. */\n const eventChannelLive = live && socketStatusRef.current === \"open\";\n /** Consumer's opt-in idle cadence (off by default). */\n const idleInterval = pollingInterval && pollingInterval > 0 ? pollingInterval : false;\n\n /**\n * On-chain reads accelerate to {@link ACCELERATED_POLLING_INTERVAL} **only** while a\n * row awaits on-chain confirmation (`WRITE_ONCHAIN` / `WRITE_ONCHAIN_CLOSE` /\n * `CLOSING`) — the RPC-lag retry. Otherwise: no idle poll when the event channel is\n * live (events drive updates) unless the consumer opted into `idleInterval`; a\n * baseline fallback when the channel is down so state still progresses. Read from the\n * previous result because the queries set their interval before this render's result\n * exists (an on-chain poll / notification re-render advances it a tick later).\n */\n const accelerateOnchain = previousRef.current ? shouldAccelerateOnchainReads(previousRef.current) : false;\n const onchainIdleInterval = eventChannelLive ? idleInterval : idleInterval || FALLBACK_POLLING_INTERVAL;\n const refetchInterval = active ? (accelerateOnchain ? ACCELERATED_POLLING_INTERVAL : onchainIdleInterval) : false;\n\n /**\n * Hedger feeds are event-driven while the channel is live: the open/close mutation\n * invalidates the matching feed once and the notifications advance the stage, so they\n * do not poll (unless the consumer opted into `idleInterval`). When the channel is\n * down they poll while the matching flow is in flight, to track it without\n * notifications. `hasOpenIntent` / `hasCloseIntent` come from the previous tick.\n */\n const previousQuotes = previousRef.current?.quotes;\n const hasOpenIntent = previousQuotes?.some((quote) => OPEN_INTENT_LIFECYCLES.has(quote.lifecycle)) ?? false;\n const hasCloseIntent = previousQuotes?.some((quote) => CLOSE_INTENT_LIFECYCLES.has(quote.lifecycle)) ?? false;\n const feedInterval = (hasIntent: boolean) =>\n !eventChannelLive && hasIntent ? ACCELERATED_POLLING_INTERVAL : idleInterval;\n const instantOpensInterval = active && wantInstantOpens ? feedInterval(hasOpenIntent) : false;\n const instantClosesInterval = active && wantInstantCloses ? feedInterval(hasCloseIntent) : false;\n\n /**\n * Off-chain hedger reads — always scoped to the sub-account, never the VAs. Kept\n * `enabled` so a mutation's `onSuccess` invalidation can fetch them on demand, but\n * polled only while the matching flow is in flight (see the interval gates above).\n */\n const instantOpens = useInstantOpens({\n partyA: partyA as Address,\n chainId: resolvedChainId,\n query: { enabled: active && wantInstantOpens, refetchInterval: instantOpensInterval },\n });\n\n const instantCloses = useInstantCloses({\n partyA: partyA as Address,\n chainId: resolvedChainId,\n query: { enabled: active && wantInstantCloses, refetchInterval: instantClosesInterval },\n });\n\n /** The sub-account's existing on-chain VAs. Empty (harmless) when `partyA` is itself a VA. */\n const virtualAccounts = useVirtualAccountsAddressesOfSubAccount({\n subAccount: partyA,\n chainId: resolvedChainId,\n query: { enabled: active && includeVirtualAccounts, refetchInterval },\n });\n\n /**\n * Brand-new-VA bridge: a position can open in a market the sub-account has\n * never traded, landing in a freshly created VA that is not yet in\n * `getVirtualAccountsAddressesOfSubAccount`. The predicted VA below covers it\n * while the instant-open is pending; once it lands on-chain the hedger drops the\n * open and the prediction with it, so VA retention (`retained`, above) keeps the\n * VA in the poll set across the hand-off — the row transitions to `ONCHAIN` in\n * place instead of flickering out and back.\n */\n\n /**\n * Predict the VA each pending instant-open will land in. The set of opens is\n * dynamic, but `useQueries` over a dynamic array is allowed — the hook count\n * stays one regardless of length.\n */\n const pendingOpens = wantInstantOpens ? (instantOpens.data ?? []) : [];\n const predictedVas = useQueries({\n queries: pendingOpens.map((open) => ({\n ...getPredictedNextVirtualAccountQueryOptions(config, {\n subAccount: partyA,\n chainId: resolvedChainId,\n isolationType: isolationTypeForSide(open.positionType),\n symbolId: BigInt(open.marketId),\n query: { enabled: active && includeVirtualAccounts, refetchInterval },\n }),\n })),\n combine: (results) => ({\n byTempId: pendingOpens.reduce<Record<number, Address>>((map, open, index) => {\n const predicted = results[index]?.data;\n if (predicted) map[open.tempQuoteId] = getAddress(predicted);\n return map;\n }, {}),\n addresses: results.map((result) => result.data).filter((address): address is Address => Boolean(address)),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n }),\n });\n\n const [notifications, setNotifications] = useState<Notification[]>([]);\n const { status: socketStatus } = useNotifications({\n account: partyA,\n chainId: resolvedChainId,\n enabled: active && live,\n onNotification: useCallback((notification: Notification) => {\n setNotifications((prev) => [...prev, notification].slice(-NOTIFICATION_BUFFER_LIMIT));\n // Solver notification carries both `tempQuoteId` and the on-chain\n // `quoteId` on the frame that anchors a pre-chain quote. Link the two\n // in the TP/SL store so a record that was written under the temp id\n // (via `useSetQuoteTpSl` post-instant-open) is instantly reachable via\n // the on-chain id once the anchor lands — no re-fetch needed.\n const tempId = notification.tempQuoteId;\n const onchainStr = notification.quoteId;\n if (tempId && onchainStr && onchainStr !== `${tempId}`) {\n useTpSlStore.getState().link(BigInt(tempId), BigInt(onchainStr));\n }\n /**\n * Refetch the authoritative reads off the live event so both tables stay in\n * sync without polling. The on-chain reads always refresh (any lifecycle event\n * can change them); the hedger feeds refresh only for the matching flow, so an\n * open event never re-fetches the closes feed and vice versa. Kinds are\n * accumulated across the debounce window because a burst can mix open and close\n * events. Scoped to this chain (`configKey`); React Query dedups concurrent\n * refetches of the same query.\n */\n const kind = classifyQuoteNotificationAction(notification.lastSeenAction);\n const pending = pendingInvalidationRef.current;\n if (kind === \"open\") pending.opens = true;\n else if (kind === \"close\") pending.closes = true;\n else {\n pending.opens = true;\n pending.closes = true;\n }\n if (invalidateTimerRef.current) clearTimeout(invalidateTimerRef.current);\n invalidateTimerRef.current = setTimeout(() => {\n const client = queryClientRef.current;\n const scope = { configKey: configKeyRef.current };\n const { opens, closes } = pendingInvalidationRef.current;\n pendingInvalidationRef.current = { opens: false, closes: false };\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAOpenPositionsQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAPendingQuotesQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getQuoteQueryKey, scope) });\n if (opens) void client.invalidateQueries({ predicate: predicateMatch(getInstantOpensQueryKey, scope) });\n if (closes) void client.invalidateQueries({ predicate: predicateMatch(getInstantClosesQueryKey, scope) });\n }, NOTIFICATION_INVALIDATE_DEBOUNCE);\n }, []),\n });\n\n /** Mirror the live socket status into the ref the interval gating reads next render. */\n socketStatusRef.current = socketStatus;\n\n /**\n * One-shot re-sync when the notifications socket **reconnects**. The stream does not\n * replay messages missed while it was down, so on a re-`open` we invalidate the reads\n * once to catch up — this replaces a continuous fallback poll. The first connect is\n * skipped (the queries already fetched on mount).\n */\n const hasBeenOpenRef = useRef(false);\n useEffect(() => {\n if (!live || socketStatus !== \"open\") return;\n if (hasBeenOpenRef.current) {\n const client = queryClientRef.current;\n const scope = { configKey: configKeyRef.current };\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAOpenPositionsQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getPartyAPendingQuotesQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getQuoteQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getInstantOpensQueryKey, scope) });\n void client.invalidateQueries({ predicate: predicateMatch(getInstantClosesQueryKey, scope) });\n }\n hasBeenOpenRef.current = true;\n }, [socketStatus, live]);\n\n /** Clear the pending invalidation timer on unmount. */\n useEffect(() => {\n return () => {\n if (invalidateTimerRef.current) clearTimeout(invalidateTimerRef.current);\n };\n }, []);\n\n /** Distinct VA addresses seen on the notifications stream. */\n const notificationVas = useMemo<Address[]>(() => {\n const seen = new Set<string>();\n const result: Address[] = [];\n for (const notification of notifications) {\n if (!notification.vaAddress) continue;\n const checksummed = getAddress(notification.vaAddress);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) continue;\n seen.add(lower);\n result.push(checksummed);\n }\n return result;\n }, [notifications]);\n\n /**\n * The deduplicated (case-insensitive), checksummed set of on-chain accounts to\n * read quotes across — the sub-account, its VAs, the predicted VAs, the\n * notification VAs, and any extras. Order: sub-account first, then the rest.\n */\n const accounts = useMemo<Address[]>(() => {\n if (!active || !partyA) return [];\n const seen = new Set<string>();\n const result: Address[] = [];\n const add = (address: Address) => {\n const checksummed = getAddress(address);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) return;\n seen.add(lower);\n result.push(checksummed);\n };\n add(partyA);\n if (includeVirtualAccounts) {\n for (const virtualAccount of virtualAccounts.data ?? []) add(virtualAccount);\n for (const predicted of predictedVas.addresses) add(predicted);\n for (const notificationVa of notificationVas) add(notificationVa);\n for (const retainedVa of retainedVas) add(retainedVa);\n }\n for (const extra of extraAccounts ?? []) add(extra);\n return result;\n }, [\n active,\n partyA,\n includeVirtualAccounts,\n virtualAccounts.data,\n predictedVas.addresses,\n notificationVas,\n retainedVas,\n extraAccounts,\n ]);\n\n /**\n * Accumulate every VA we discover (list + predicted + notification) into the\n * retained set so none ever leaves the poll set mid-hand-off. Monotonic per\n * owner; resets when the owner `(chainId, partyA)` changes. Setting state only\n * when something new appears keeps this from looping.\n */\n useEffect(() => {\n if (!retentionOwner) return;\n const discovered = [...(virtualAccounts.data ?? []), ...predictedVas.addresses, ...notificationVas];\n setRetained((prev) => {\n const carryOver = prev.owner === retentionOwner ? prev.vas : [];\n const seen = new Set(carryOver.map((address) => address.toLowerCase()));\n const next = [...carryOver];\n let changed = prev.owner !== retentionOwner;\n for (const va of discovered) {\n const checksummed = getAddress(va);\n const lower = checksummed.toLowerCase();\n if (seen.has(lower)) continue;\n seen.add(lower);\n next.push(checksummed);\n changed = true;\n }\n return changed ? { owner: retentionOwner, vas: next } : prev;\n });\n }, [retentionOwner, virtualAccounts.data, predictedVas.addresses, notificationVas]);\n\n /** On-chain open positions, fanned out across every account, flattened to one `Quote[]`. */\n const openPositions = useQueries({\n queries: accounts.map((account) => {\n const options = getPartyAOpenPositionsQueryOptions(config, {\n partyA: account,\n chainId: resolvedChainId,\n query: { enabled: active && wantOpenPositions, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n quotes: results.flatMap((result) => result.data ?? []),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n refetch: () => results.forEach((result) => result.refetch()),\n }),\n });\n /** Pending quote ids, fanned out across every account, then hydrated per id. */\n const pendingQuoteIds = useQueries({\n queries: accounts.map((account) => {\n const options = getPartyAPendingQuotesQueryOptions(config, {\n partyA: account,\n chainId: resolvedChainId,\n query: { enabled: active && wantPendingQuotes, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n ids: results.flatMap((result) => result.data ?? []),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n refetch: () => results.forEach((result) => result.refetch()),\n }),\n });\n\n const hydratedPending = useQueries({\n queries: pendingQuoteIds.ids.map((quoteId) => {\n const options = getQuoteQueryOptions(config, {\n quoteId,\n chainId: resolvedChainId,\n query: { enabled: active && wantPendingQuotes, refetchInterval },\n });\n return {\n ...options,\n queryFn: async () => {\n try {\n return await options.queryFn();\n } catch (err) {\n throw normalizeSymmError(err);\n }\n },\n };\n }),\n combine: (results) => ({\n quotes: results.map((result) => result.data).filter((quote): quote is Quote => Boolean(quote)),\n isLoading: results.some((result) => result.isLoading),\n isFetching: results.some((result) => result.isFetching),\n error: (results.find((result) => result.error)?.error as SymmioRequestError | undefined) ?? null,\n }),\n });\n\n const result = useMemo<ReconcileQuotesResult>(() => {\n if (!active || !partyA) {\n previousRef.current = undefined;\n retainedAnchorsRef.current = [];\n return { quotes: [], links: {}, pendingAnchors: [] };\n }\n const seededInstantOpens = [...(instantOpens.data ?? []), ...Object.values(optimisticEntries)];\n const next = reconcileQuotes({\n partyA,\n onchainPositions: wantOpenPositions ? openPositions.quotes : [],\n onchainPendingQuotes: wantPendingQuotes ? hydratedPending.quotes : [],\n instantOpens: wantInstantOpens ? seededInstantOpens : [],\n instantCloses: wantInstantCloses ? (instantCloses.data ?? []) : [],\n instantOpenVaByTempId: predictedVas.byTempId,\n notifications,\n retainedAnchors: retainedAnchorsRef.current,\n });\n previousRef.current = next;\n retainedAnchorsRef.current = next.pendingAnchors;\n return next;\n }, [\n active,\n partyA,\n openPositions.quotes,\n hydratedPending.quotes,\n instantOpens.data,\n instantCloses.data,\n predictedVas.byTempId,\n optimisticEntries,\n notifications,\n wantOpenPositions,\n wantPendingQuotes,\n wantInstantOpens,\n wantInstantCloses,\n ]);\n\n const byKey = useMemo<Record<string, UnifiedQuote>>(() => {\n const map: Record<string, UnifiedQuote> = {};\n for (const quote of result.quotes) map[quote.key] = quote;\n return map;\n }, [result.quotes]);\n\n /**\n * Drop optimistic seeds once their `tempQuoteId` has been **confirmed by the\n * on-chain read** — the linked row carries the polled struct (`raw.onchain`).\n * Clearing on the notification link alone would drop the seed while the row is\n * still `WRITE_ONCHAIN`, before any source but the notification holds it, so the\n * row would flicker out until the RPC catches up.\n */\n useEffect(() => {\n const settled = Object.keys(optimisticEntries)\n .map(Number)\n .filter((tempQuoteId) => {\n const key = result.links[tempQuoteId];\n return key !== undefined && byKey[key]?.raw.onchain !== undefined;\n });\n if (settled.length > 0) clearSettled(settled);\n }, [result, byKey, optimisticEntries, clearSettled]);\n\n const isLoading =\n (active && includeVirtualAccounts && virtualAccounts.isLoading) ||\n (active && includeVirtualAccounts && predictedVas.isLoading) ||\n (active && wantOpenPositions && openPositions.isLoading) ||\n (active && wantPendingQuotes && (pendingQuoteIds.isLoading || hydratedPending.isLoading)) ||\n (active && wantInstantOpens && instantOpens.isLoading) ||\n (active && wantInstantCloses && instantCloses.isLoading);\n\n const isFetching =\n (includeVirtualAccounts && (virtualAccounts.isFetching || predictedVas.isFetching)) ||\n (wantOpenPositions && openPositions.isFetching) ||\n (wantPendingQuotes && (pendingQuoteIds.isFetching || hydratedPending.isFetching)) ||\n (wantInstantOpens && instantOpens.isFetching) ||\n (wantInstantCloses && instantCloses.isFetching);\n\n const error =\n openPositions.error ??\n pendingQuoteIds.error ??\n hydratedPending.error ??\n virtualAccounts.error ??\n instantOpens.error ??\n instantCloses.error ??\n null;\n\n /**\n * `openPositions` / `pendingQuoteIds` are `useQueries` fan-outs (no single\n * query handle), so their combined results expose a `refetch` that fires every\n * per-account query. Refetching the ids re-drives the per-id hydration.\n */\n const refetchOpenPositions = openPositions.refetch;\n const refetchPendingQuoteIds = pendingQuoteIds.refetch;\n const refetch = useCallback(() => {\n if (includeVirtualAccounts) virtualAccounts.refetch();\n if (wantOpenPositions) refetchOpenPositions();\n if (wantPendingQuotes) refetchPendingQuoteIds();\n if (wantInstantOpens) instantOpens.refetch();\n if (wantInstantCloses) instantCloses.refetch();\n }, [\n includeVirtualAccounts,\n wantOpenPositions,\n wantPendingQuotes,\n wantInstantOpens,\n wantInstantCloses,\n virtualAccounts,\n refetchOpenPositions,\n refetchPendingQuoteIds,\n instantOpens,\n instantCloses,\n ]);\n\n return {\n quotes: result.quotes,\n byKey,\n accounts,\n isLoading: Boolean(isLoading),\n isFetching: Boolean(isFetching),\n socketStatus: live ? socketStatus : \"closed\",\n error,\n refetch,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Notification } from '@symmio/trading-core';
2
+ /**
3
+ * `true` when a notification marks an on-chain settle — a `SUCCESS` frame whose
4
+ * `lastSeenAction` is an open-anchor or close-fill. Live balance reads refetch
5
+ * on exactly these.
6
+ */
7
+ export declare function isSettleNotification(notification: Notification): boolean;
8
+ /**
9
+ * Delays (ms) at which a live balance read refetches after a settle
10
+ * notification. The on-chain read lags the anchor block, so a single immediate
11
+ * read often returns the pre-settle value; a short spaced burst lets the updated
12
+ * balance land without a manual refresh.
13
+ */
14
+ export declare const SETTLE_REFETCH_DELAYS_MS: readonly [0, 2000, 5000];
15
+ //# sourceMappingURL=is-settle-notification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-settle-notification.d.ts","sourceRoot":"","sources":["../../src/websocket/is-settle-notification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAU3E;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAMxE;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0BAA2B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { NotificationType as e } from "@symmio/trading-core";
2
+ //#region src/websocket/is-settle-notification.ts
3
+ var t = new Set(["SendQuoteTransaction", "FillMarketOrderInstantClose"]);
4
+ function n(n) {
5
+ return n.type === e.SUCCESS && n.lastSeenAction !== null && t.has(n.lastSeenAction);
6
+ }
7
+ var r = [
8
+ 0,
9
+ 2e3,
10
+ 5e3
11
+ ];
12
+ //#endregion
13
+ export { r as SETTLE_REFETCH_DELAYS_MS, n as isSettleNotification };
14
+
15
+ //# sourceMappingURL=is-settle-notification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-settle-notification.js","names":[],"sources":["../../src/websocket/is-settle-notification.ts"],"sourcesContent":["import { NotificationType, type Notification } from \"@symmio/trading-core\";\n\n/**\n * Solver actions that mean a position settled on-chain — an open anchored\n * (margin locked) or a close filled (PnL realized, margin unlocked). These are\n * the frames that move a subaccount's balance; the earlier request / price-fill\n * frames do not. Mirrors core's `OPEN_ANCHOR_ACTIONS` ∪ `CLOSE_FILL_ACTIONS`.\n */\nconst SETTLE_ACTIONS = new Set<string>([\"SendQuoteTransaction\", \"FillMarketOrderInstantClose\"]);\n\n/**\n * `true` when a notification marks an on-chain settle — a `SUCCESS` frame whose\n * `lastSeenAction` is an open-anchor or close-fill. Live balance reads refetch\n * on exactly these.\n */\nexport function isSettleNotification(notification: Notification): boolean {\n return (\n notification.type === NotificationType.SUCCESS &&\n notification.lastSeenAction !== null &&\n SETTLE_ACTIONS.has(notification.lastSeenAction)\n );\n}\n\n/**\n * Delays (ms) at which a live balance read refetches after a settle\n * notification. The on-chain read lags the anchor block, so a single immediate\n * read often returns the pre-settle value; a short spaced burst lets the updated\n * balance land without a manual refresh.\n */\nexport const SETTLE_REFETCH_DELAYS_MS = [0, 2000, 5000] as const;\n"],"mappings":";;AAQA,IAAM,IAAiB,IAAI,IAAY,CAAC,wBAAwB,6BAA6B,CAAC;AAO9F,SAAgB,EAAqB,GAAqC;CACxE,OACE,EAAa,SAAS,EAAiB,WACvC,EAAa,mBAAmB,QAChC,EAAe,IAAI,EAAa,cAAc;AAElD;AAQA,IAAa,IAA2B;CAAC;CAAG;CAAM;AAAI"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@symmio/trading-react",
3
- "version": "0.2.0",
3
+ "version": "1.1.0",
4
4
  "description": "React adapter for the SYMMIO SDK. Hooks, providers, and React-bound ergonomics on top of @symmio/trading-core.",
5
5
  "license": "MIT",
6
- "homepage": "https://github.com/SYMM-IO/SYMM-Frontier/tree/main/packages/trading-react#readme",
6
+ "homepage": "https://github.com/SYMM-IO/Trading-SDK/tree/main/packages/trading-react#readme",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/SYMM-IO/SYMM-Frontier.git",
9
+ "url": "git+https://github.com/SYMM-IO/Trading-SDK.git",
10
10
  "directory": "packages/trading-react"
11
11
  },
12
- "bugs": "https://github.com/SYMM-IO/SYMM-Frontier/issues",
12
+ "bugs": "https://github.com/SYMM-IO/Trading-SDK/issues",
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "main": "./dist/index.js",
@@ -71,7 +71,7 @@
71
71
  "@tanstack/react-query": "^5.100.0",
72
72
  "decimal.js": "^10.4.3",
73
73
  "zustand": "^5.0.0",
74
- "@symmio/trading-core": "^0.2.0"
74
+ "@symmio/trading-core": "^1.1.0"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "react": "^19.0.0",
@@ -87,7 +87,6 @@
87
87
  "@types/react-dom": "^19.2.3",
88
88
  "@vitejs/plugin-react": "^6.0.1",
89
89
  "@vitest/coverage-v8": "^4.1.10",
90
- "@wagmi/connectors": "^8.0.0",
91
90
  "eslint": "^9.39.4",
92
91
  "happy-dom": "^16.0.0",
93
92
  "react": "^19.2.5",
@@ -98,8 +97,8 @@
98
97
  "vite-plugin-dts": "^5.0.0",
99
98
  "vitest": "^4.1.10",
100
99
  "wagmi": "^3.4.2",
101
- "@symmio/eslint-config": "^0.1.2",
102
- "@symmio/typescript-config": "^0.1.1"
100
+ "@symmio/eslint-config": "^1.0.0",
101
+ "@symmio/typescript-config": "^1.0.0"
103
102
  },
104
103
  "scripts": {
105
104
  "build": "vite build && node ../../scripts/fix-dts-extensions.mjs dist",