@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 +110 -5
- package/contexts/SettingsContext.d.ts +8 -1
- package/contexts/SettingsContext.jsx +55 -51
- package/contexts/WalletContext.d.ts +1 -1
- package/contexts/WalletContext.jsx +330 -463
- package/fetchers/useFetchBalances.js +32 -92
- package/hooks/keypair.d.ts +1 -1
- package/hooks/keypair.js +7 -7
- package/hooks/useCoinMetadataMap.js +12 -69
- package/hooks/useIsAndroid.jsx +1 -1
- package/hooks/useIsTouchscreen.jsx +3 -5
- package/hooks/useIsiOS.jsx +4 -6
- package/hooks/useLedgerHashDialog.js +16 -61
- package/hooks/useRefreshOnBalancesChange.js +28 -81
- package/lib/connector.js +32 -77
- package/lib/router.d.ts +1 -1
- package/lib/router.js +2 -2
- package/lib/toasts.jsx +32 -40
- package/lib/track.js +3 -3
- package/package.json +85 -1
- package/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,113 @@
|
|
|
1
|
-
# sui-fe-next
|
|
1
|
+
# @suilend/sui-fe-next
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
+
Providers (both are composed in each app's `_app.tsx`, in this order):
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7
|
+
const defaultContextValue = {
|
|
8
|
+
rpcs: RPCS,
|
|
19
9
|
rpc: RPCS[0],
|
|
20
|
-
setRpcId:
|
|
10
|
+
setRpcId: () => {
|
|
21
11
|
throw Error("SettingsContextProvider not initialized");
|
|
22
12
|
},
|
|
23
|
-
setRpcUrl:
|
|
13
|
+
setRpcUrl: () => {
|
|
24
14
|
throw Error("SettingsContextProvider not initialized");
|
|
25
15
|
},
|
|
26
16
|
explorer: EXPLORERS[0],
|
|
27
|
-
setExplorerId:
|
|
17
|
+
setExplorerId: () => {
|
|
28
18
|
throw Error("SettingsContextProvider not initialized");
|
|
29
19
|
},
|
|
30
20
|
gasBudget: "",
|
|
31
|
-
setGasBudget:
|
|
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
|
-
|
|
48
|
-
export
|
|
49
|
-
export function SettingsContextProvider(
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
68
|
+
const [gasBudget, setGasBudget] = useLocalStorage("gasBudget", defaultContextValue.gasBudget);
|
|
67
69
|
// Sui clients
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
|
73
|
+
export declare function WalletContextProvider({ appName, children }: WalletContextProviderProps): import("react").JSX.Element;
|
|
74
74
|
export {};
|