@suilend/sui-fe-next 3.0.9 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,113 @@
1
- # sui-fe-next
1
+ # @suilend/sui-fe-next
2
2
 
3
- A collection of TypeScript frontend components and hooks.
3
+ The React/Next layer shared by the Suilend-ecosystem frontends
4
+ (`springsui-web`, `steamm-web`, `suilend-web`). Built on
5
+ [`@suilend/sui-fe`](../sui-fe), which holds the non-React utilities.
4
6
 
5
- ## Get started
7
+ Providers (both are composed in each app's `_app.tsx`, in this order):
6
8
 
7
- 1. Install `bun` [https://bun.sh/](https://bun.sh/)
8
- 2. Run `bun i`
9
+ - **`SettingsContextProvider`** / `useSettingsContext()` — RPC, explorer and
10
+ gas-budget settings, persisted to `localStorage`, plus the memoized
11
+ `suiJsonRpcClient` / `suiGrpcClient` / `suiGraphQLClient`.
12
+ - **`WalletContextProvider({ appName })`** / `useWalletContext()` — wraps
13
+ `@mysten/dapp-kit-react`'s `DAppKitProvider`; wallet discovery/connect,
14
+ impersonation, WalletConnect (Reown) sessions, and
15
+ `signExecuteAndWaitForTransaction`.
16
+
17
+ Also exported: `useFetchBalances`, `useCoinMetadataMap`,
18
+ `useRefreshOnBalancesChange`, `useLedgerHashDialog`, `useLastSignedTransaction`,
19
+ the `useIsAndroid`/`useIsiOS`/`useIsTouchscreen` platform hooks, the
20
+ `shallowPushQuery`/`shallowReplaceQuery` router helpers, Sonner-based toasts, and
21
+ Mixpanel tracking.
22
+
23
+ There are no rendered UI components here — dialogs and layout live in the apps.
24
+
25
+ ## Working in here
26
+
27
+ This is a workspace package in the `perpetual-ui` Yarn 4 monorepo — install from
28
+ the repo root (`yarn install`), not from this directory.
29
+
30
+ ```sh
31
+ yarn workspace @suilend/sui-fe-next typecheck # tsgo --noEmit
32
+ yarn workspace @suilend/sui-fe-next lint:ci # eslint (repo-wide config, no local overrides)
33
+ yarn workspace @suilend/sui-fe-next build # tsc -> ./dist (publish artifact only)
34
+ ```
35
+
36
+ The three apps depend on this package as `"@suilend/sui-fe-next": "workspace:^"`
37
+ and list it in Next's `transpilePackages`, so **they compile this TypeScript
38
+ source directly** — a change here reaches the apps with no build and no publish
39
+ step. `dist/` exists only to be published; nothing in this repo consumes it.
40
+
41
+ Formatting and lint come from the repo root (`.prettierrc`,
42
+ `@bluefin/eslint-config/base`). There are no package-local overrides — don't add
43
+ one without a reason.
44
+
45
+ Two things to know before editing:
46
+
47
+ - **`react`/`react-dom` are peer dependencies pinned at 19.2.3**, matching the
48
+ monorepo's root `resolutions`. Never add them as dependencies.
49
+ - **`tsconfig` uses `jsx: preserve`**, so the build emits `.jsx` for `.tsx`
50
+ sources and consumers transpile them. That's why this package cannot extend
51
+ `@bluefin/typescript-config/react-library.json` (which sets `jsx: react-jsx`),
52
+ and why every consuming app must keep it in `transpilePackages`.
53
+
54
+ ## Releasing
55
+
56
+ Published by `.github/workflows/sui_fe_next_publish.yaml` using npm **OIDC Trusted Publishing** —
57
+ no npm token is stored or rotated.
58
+
59
+ > Note: npm does **not** generate provenance attestations for packages published
60
+ > from a private repository, so these releases are unsigned despite using OIDC.
61
+ > Trusted Publishing still replaces the token; only the attestation is missing.
62
+
63
+ To release:
64
+
65
+ 1. Bump `version` in `package.json` and merge.
66
+ 2. Push the matching tag:
67
+
68
+ ```sh
69
+ git tag release-sui-fe-next-v<version> && git push origin release-sui-fe-next-v<version>
70
+ ```
71
+
72
+ Use `release-sui-fe-next-prerelease-<version>` to publish under the `next` dist-tag.
73
+
74
+ ### Two phases, one approval
75
+
76
+ The tag push runs a **`stage`** job immediately — everything reversible:
77
+
78
+ - guards that the tag version matches `package.json` and isn't already on npm
79
+ (so a bad tag fails fast instead of burning an approval);
80
+ - builds, prepares the publish manifest, and `npm pack`s the tarball;
81
+ - cuts the GitHub Release as a **prerelease**, with the tarball attached and
82
+ notes listing only the commits that touched `packages/sui-fe-next` since the previous
83
+ `release-sui-fe-next-v*` tag. (The repo-wide "Release Trade" notes cover every commit
84
+ in the monorepo and aren't useful to a consumer of this package.)
85
+
86
+ The **`publish`** job then waits on the **`sui-prod`** environment's
87
+ `bluefin-internal` reviewers — the same gate as the app deploys. On approval it
88
+ rebuilds, asserts the tarball's sha256 still matches the reviewed artifact
89
+ (`npm pack` is byte-reproducible, so a mismatch means something changed and the
90
+ publish is refused), publishes to npm, and promotes the Release out of
91
+ prerelease.
92
+
93
+ So the reviewer approves with the notes and the exact artifact in front of them,
94
+ and the only gated step is the irreversible one. Releases stay `--latest=false`
95
+ so the "Latest" badge remains on the trade release.
96
+
97
+ If approval is declined, the prerelease Release remains as a record of what was
98
+ staged — delete it if you don't want it.
99
+
100
+ **One-time setup:** on npmjs.com → @suilend/sui-fe-next → *Settings → Trusted Publisher →
101
+ GitHub Actions*, with organization `fireflyprotocol`, repository `perpetual-ui`,
102
+ workflow `sui_fe_next_publish.yaml`.
103
+
104
+ The published manifest is generated by
105
+ [`scripts/prepare-npm-dist.mjs`](../../scripts/prepare-npm-dist.mjs), which
106
+ rewrites the source-facing entry points (`./src/**/*.ts(x)`) to dist-relative
107
+ ones, flips `private`, and resolves this package's `@suilend/sui-fe`
108
+ `workspace:^` range to a real `^<version>`. That step is required because the npm
109
+ CLI ignores `publishConfig` overrides for `main`/`types`/`exports` — see the
110
+ script header.
111
+
112
+ Publishing only matters for **external** consumers; in-repo consumers always use
113
+ the workspace copy.
@@ -4,6 +4,7 @@ import { SuiGrpcClient } from "@mysten/sui/grpc";
4
4
  import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
5
5
  import { Explorer, ExplorerId, Rpc, RpcId } from "@suilend/sui-fe";
6
6
  interface SettingsContext {
7
+ rpcs: Rpc[];
7
8
  rpc: Rpc;
8
9
  setRpcId: (id: RpcId) => void;
9
10
  setRpcUrl: (url: string) => void;
@@ -17,5 +18,11 @@ interface SettingsContext {
17
18
  }
18
19
  declare const SettingsContext: import("react").Context<SettingsContext>;
19
20
  export declare const useSettingsContext: () => SettingsContext;
20
- export declare function SettingsContextProvider({ children }: PropsWithChildren): import("react").JSX.Element;
21
+ export type SettingsContextProviderProps = PropsWithChildren<{
22
+ /** RPCs to offer, in display order. Defaults to `RPCS`. Pass a stable reference. */
23
+ rpcs?: Rpc[];
24
+ /** Selected when nothing is persisted, or the persisted id isn't in `rpcs`. Defaults to `rpcs[0]`. */
25
+ defaultRpcId?: RpcId;
26
+ }>;
27
+ export declare function SettingsContextProvider({ children, rpcs: rpcsProp, defaultRpcId, }: SettingsContextProviderProps): import("react").JSX.Element;
21
28
  export {};
@@ -1,34 +1,24 @@
1
- var __assign = (this && this.__assign) || function () {
2
- __assign = Object.assign || function(t) {
3
- for (var s, i = 1, n = arguments.length; i < n; i++) {
4
- s = arguments[i];
5
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
- t[p] = s[p];
7
- }
8
- return t;
9
- };
10
- return __assign.apply(this, arguments);
11
- };
12
- import { createContext, useContext, useMemo } from "react";
1
+ import { createContext, useContext, useEffect, useMemo } from "react";
13
2
  import { SuiGraphQLClient } from "@mysten/sui/graphql";
14
3
  import { SuiGrpcClient } from "@mysten/sui/grpc";
15
4
  import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
16
5
  import { useLocalStorage } from "usehooks-ts";
17
6
  import { EXPLORERS, RPCS, RPC_GRAPHQL_URL, RpcId, } from "@suilend/sui-fe";
18
- var defaultContextValue = {
7
+ const defaultContextValue = {
8
+ rpcs: RPCS,
19
9
  rpc: RPCS[0],
20
- setRpcId: function () {
10
+ setRpcId: () => {
21
11
  throw Error("SettingsContextProvider not initialized");
22
12
  },
23
- setRpcUrl: function () {
13
+ setRpcUrl: () => {
24
14
  throw Error("SettingsContextProvider not initialized");
25
15
  },
26
16
  explorer: EXPLORERS[0],
27
- setExplorerId: function () {
17
+ setExplorerId: () => {
28
18
  throw Error("SettingsContextProvider not initialized");
29
19
  },
30
20
  gasBudget: "",
31
- setGasBudget: function () {
21
+ setGasBudget: () => {
32
22
  throw Error("SettingsContextProvider not initialized");
33
23
  },
34
24
  suiJsonRpcClient: new SuiJsonRpcClient({
@@ -44,43 +34,57 @@ var defaultContextValue = {
44
34
  network: "mainnet",
45
35
  }),
46
36
  };
47
- var SettingsContext = createContext(defaultContextValue);
48
- export var useSettingsContext = function () { return useContext(SettingsContext); };
49
- export function SettingsContextProvider(_a) {
50
- var children = _a.children;
37
+ const SettingsContext = createContext(defaultContextValue);
38
+ export const useSettingsContext = () => useContext(SettingsContext);
39
+ export function SettingsContextProvider({ children, rpcs: rpcsProp, defaultRpcId, }) {
51
40
  // RPC
52
- var _b = useLocalStorage("rpcId", defaultContextValue.rpc.id), rpcId = _b[0], setRpcId = _b[1];
53
- var _c = useLocalStorage("rpcUrl", ""), rpcUrl = _c[0], setRpcUrl = _c[1];
54
- var rpc = useMemo(function () {
55
- var _a;
56
- return rpcId === RpcId.CUSTOM
57
- ? __assign(__assign({}, RPCS.find(function (_rpc) { return _rpc.id === RpcId.CUSTOM; })), { url: rpcUrl }) : ((_a = RPCS.find(function (_rpc) { return _rpc.id === rpcId; })) !== null && _a !== void 0 ? _a : RPCS[0]);
58
- }, [rpcId, rpcUrl]);
41
+ const rpcs = useMemo(() => {
42
+ const result = rpcsProp ?? RPCS;
43
+ if (result.length === 0)
44
+ throw new Error("SettingsContextProvider: `rpcs` must not be empty");
45
+ return result;
46
+ }, [rpcsProp]);
47
+ const fallbackRpc = useMemo(() => rpcs.find((_rpc) => _rpc.id === defaultRpcId) ?? rpcs[0], [rpcs, defaultRpcId]);
48
+ const [rpcId, setRpcId] = useLocalStorage("rpcId", fallbackRpc.id);
49
+ const [rpcUrl, setRpcUrl] = useLocalStorage("rpcUrl", "");
50
+ const isRpcIdOffered = rpcs.some((_rpc) => _rpc.id === rpcId);
51
+ useEffect(() => {
52
+ if (!isRpcIdOffered)
53
+ setRpcId(fallbackRpc.id);
54
+ }, [isRpcIdOffered, fallbackRpc.id, setRpcId]);
55
+ const rpc = useMemo(() => {
56
+ if (!isRpcIdOffered)
57
+ return fallbackRpc;
58
+ if (rpcId === RpcId.CUSTOM) {
59
+ const customRpc = rpcs.find((_rpc) => _rpc.id === RpcId.CUSTOM);
60
+ return customRpc ? { ...customRpc, url: rpcUrl } : fallbackRpc;
61
+ }
62
+ return rpcs.find((_rpc) => _rpc.id === rpcId) ?? fallbackRpc;
63
+ }, [isRpcIdOffered, fallbackRpc, rpcId, rpcUrl, rpcs]);
59
64
  // Explorer
60
- var _d = useLocalStorage("explorerId", defaultContextValue.explorer.id), explorerId = _d[0], setExplorerId = _d[1];
61
- var explorer = useMemo(function () {
62
- var _a;
63
- return (_a = EXPLORERS.find(function (_explorer) { return _explorer.id === explorerId; })) !== null && _a !== void 0 ? _a : EXPLORERS[0];
64
- }, [explorerId]);
65
+ const [explorerId, setExplorerId] = useLocalStorage("explorerId", defaultContextValue.explorer.id);
66
+ const explorer = useMemo(() => EXPLORERS.find((_explorer) => _explorer.id === explorerId) ?? EXPLORERS[0], [explorerId]);
65
67
  // Gas budget
66
- var _e = useLocalStorage("gasBudget", defaultContextValue.gasBudget), gasBudget = _e[0], setGasBudget = _e[1];
68
+ const [gasBudget, setGasBudget] = useLocalStorage("gasBudget", defaultContextValue.gasBudget);
67
69
  // Sui clients
68
- var suiJsonRpcClient = useMemo(function () { return new SuiJsonRpcClient({ url: rpc.url, network: "mainnet" }); }, [rpc.url]);
69
- var suiGrpcClient = useMemo(function () { return new SuiGrpcClient({ baseUrl: rpc.url, network: "mainnet" }); }, [rpc.url]);
70
- var suiGraphQLClient = useMemo(function () { return new SuiGraphQLClient({ url: RPC_GRAPHQL_URL, network: "mainnet" }); }, []);
70
+ const suiJsonRpcClient = useMemo(() => new SuiJsonRpcClient({ url: rpc.url, network: "mainnet" }), [rpc.url]);
71
+ const suiGrpcClient = useMemo(() => new SuiGrpcClient({ baseUrl: rpc.url, network: "mainnet" }), [rpc.url]);
72
+ const suiGraphQLClient = useMemo(() => new SuiGraphQLClient({ url: RPC_GRAPHQL_URL, network: "mainnet" }), []);
71
73
  // Context
72
- var contextValue = useMemo(function () { return ({
73
- rpc: rpc,
74
- setRpcId: setRpcId,
75
- setRpcUrl: setRpcUrl,
76
- explorer: explorer,
77
- setExplorerId: setExplorerId,
78
- gasBudget: gasBudget,
79
- setGasBudget: setGasBudget,
80
- suiJsonRpcClient: suiJsonRpcClient,
81
- suiGrpcClient: suiGrpcClient,
82
- suiGraphQLClient: suiGraphQLClient,
83
- }); }, [
74
+ const contextValue = useMemo(() => ({
75
+ rpcs,
76
+ rpc,
77
+ setRpcId,
78
+ setRpcUrl,
79
+ explorer,
80
+ setExplorerId,
81
+ gasBudget,
82
+ setGasBudget,
83
+ suiJsonRpcClient,
84
+ suiGrpcClient,
85
+ suiGraphQLClient,
86
+ }), [
87
+ rpcs,
84
88
  rpc,
85
89
  setRpcId,
86
90
  setRpcUrl,
@@ -93,6 +97,6 @@ export function SettingsContextProvider(_a) {
93
97
  suiGraphQLClient,
94
98
  ]);
95
99
  return (<SettingsContext.Provider value={contextValue}>
96
- {children}
97
- </SettingsContext.Provider>);
100
+ {children}
101
+ </SettingsContext.Provider>);
98
102
  }
@@ -70,5 +70,5 @@ export declare const useWalletContext: () => WalletContext;
70
70
  interface WalletContextProviderProps extends PropsWithChildren {
71
71
  appName: string;
72
72
  }
73
- export declare function WalletContextProvider({ appName, children, }: WalletContextProviderProps): import("react").JSX.Element;
73
+ export declare function WalletContextProvider({ appName, children }: WalletContextProviderProps): import("react").JSX.Element;
74
74
  export {};