@dngbuilds/zapkit-react 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,473 @@
1
+ import ZapKit, { Amount, BridgeToken, ChainId, ExternalChain, OnboardStrategy, StarkSigner, Tx, TxBuilder, accountPresets, fromAddress, getPresets } from "@dngbuilds/zapkit-core";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
+ import { QueryClient, QueryClient as QueryClient$1, QueryClientProvider, useMutation, useQuery } from "@tanstack/react-query";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/context.ts
6
+ const ZapContext = createContext(void 0);
7
+ /** Internal helper — throws a clear error outside ZapProvider */
8
+ function useZapContext() {
9
+ const ctx = useContext(ZapContext);
10
+ if (!ctx) throw new Error("🚫 ZapKit: hook used outside <ZapProvider>. Wrap your app with <ZapProvider config={...}>.");
11
+ return ctx;
12
+ }
13
+ //#endregion
14
+ //#region src/components/ZapDevPanel.tsx
15
+ function ZapDevPanel({ error, status, address }) {
16
+ if (!error && status !== "error") return null;
17
+ return /* @__PURE__ */ jsxs("div", {
18
+ style: {
19
+ position: "fixed",
20
+ bottom: 16,
21
+ right: 16,
22
+ zIndex: 9999,
23
+ maxWidth: 400,
24
+ background: "#1a1a1a",
25
+ color: "#ff4444",
26
+ borderRadius: 8,
27
+ padding: "12px 16px",
28
+ fontFamily: "monospace",
29
+ fontSize: 13,
30
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
31
+ border: "1px solid #ff4444"
32
+ },
33
+ children: [
34
+ /* @__PURE__ */ jsx("strong", { children: "⚡ ZapKit Dev Panel" }),
35
+ /* @__PURE__ */ jsxs("div", {
36
+ style: {
37
+ marginTop: 8,
38
+ color: "#fff"
39
+ },
40
+ children: ["Status: ", /* @__PURE__ */ jsx("span", {
41
+ style: { color: status === "error" ? "#ff4444" : "#4caf50" },
42
+ children: status
43
+ })]
44
+ }),
45
+ address && /* @__PURE__ */ jsxs("div", {
46
+ style: {
47
+ color: "#aaa",
48
+ marginTop: 4
49
+ },
50
+ children: [
51
+ "Address: ",
52
+ address.slice(0, 10),
53
+ "…",
54
+ address.slice(-6)
55
+ ]
56
+ }),
57
+ error && /* @__PURE__ */ jsxs("div", {
58
+ style: {
59
+ marginTop: 8,
60
+ color: "#ff4444",
61
+ wordBreak: "break-word"
62
+ },
63
+ children: ["Error: ", error.message]
64
+ })
65
+ ]
66
+ });
67
+ }
68
+ //#endregion
69
+ //#region src/provider.tsx
70
+ const STORAGE_KEY = "zapkit_wallet_connected";
71
+ /** Default QueryClient config suitable for blockchain data: longer stale times, no window-focus refetch. */
72
+ function createDefaultQueryClient() {
73
+ return new QueryClient$1({ defaultOptions: { queries: {
74
+ staleTime: 3e4,
75
+ gcTime: 5 * 6e4,
76
+ retry: 2,
77
+ refetchOnWindowFocus: false
78
+ } } });
79
+ }
80
+ /**
81
+ * Root provider for @zapkit/react. Wrap your app (or a subtree) with this.
82
+ *
83
+ * @example
84
+ * <ZapProvider config={{ network: "mainnet" }}>
85
+ * <App />
86
+ * </ZapProvider>
87
+ */
88
+ function ZapProvider({ config, queryClient: externalQC, showDevPanel = false, children }) {
89
+ const [internalQC] = useState(() => externalQC ?? createDefaultQueryClient());
90
+ return /* @__PURE__ */ jsx(QueryClientProvider, {
91
+ client: internalQC,
92
+ children: /* @__PURE__ */ jsx(ZapCore, {
93
+ config,
94
+ showDevPanel,
95
+ children
96
+ })
97
+ });
98
+ }
99
+ function ZapCore({ config, showDevPanel, children }) {
100
+ const [address, setAddress] = useState(null);
101
+ const [ens] = useState(null);
102
+ const [balances] = useState({});
103
+ const [status, setStatus] = useState("idle");
104
+ const [loading, setLoading] = useState(false);
105
+ const [error, setError] = useState(null);
106
+ const kitRef = useRef(null);
107
+ const [zapkit, setZapkit] = useState(null);
108
+ useEffect(() => {
109
+ if (!kitRef.current) {
110
+ kitRef.current = new ZapKit(config);
111
+ setZapkit(kitRef.current);
112
+ }
113
+ }, []);
114
+ /**
115
+ * Returns true when the thrown error should silently reset to idle without
116
+ * showing an error banner — covers user dismissal and transient init failures.
117
+ *
118
+ * @param isAutoReconnect — when true, also treat controller-init timeouts as
119
+ * non-fatal (the page just loaded and the iframe wasn't ready).
120
+ */
121
+ function isNonFatalError(err, isAutoReconnect = false) {
122
+ const msg = err.message.toLowerCase();
123
+ const name = (err.name ?? "").toLowerCase();
124
+ if (name === "usercancellederror" || name === "aborterror" || msg.includes("user closed") || msg.includes("user cancelled") || msg.includes("user canceled") || msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user dismissed") || msg.includes("popup closed") || msg.includes("window closed")) return true;
125
+ if (isAutoReconnect && (msg.includes("cartridge controller failed to initialize") || msg.includes("controller failed to initialize"))) return true;
126
+ return false;
127
+ }
128
+ const connect = useCallback(async (options, { isAutoReconnect = false } = {}) => {
129
+ const kit = kitRef.current;
130
+ if (!kit) return;
131
+ setStatus("connecting");
132
+ setLoading(true);
133
+ setError(null);
134
+ try {
135
+ await kit.onboard(options);
136
+ setAddress(kit.getWallet()?.address ?? null);
137
+ setStatus("connected");
138
+ localStorage.setItem(STORAGE_KEY, options.strategy);
139
+ } catch (err) {
140
+ const e = err instanceof Error ? err : new Error(String(err));
141
+ if (isNonFatalError(e, isAutoReconnect)) {
142
+ localStorage.removeItem(STORAGE_KEY);
143
+ setStatus("idle");
144
+ } else {
145
+ setError(e);
146
+ setStatus("error");
147
+ }
148
+ } finally {
149
+ setLoading(false);
150
+ }
151
+ }, []);
152
+ const disconnect = useCallback(async () => {
153
+ await kitRef.current?.disconnect();
154
+ setAddress(null);
155
+ setStatus("idle");
156
+ setError(null);
157
+ localStorage.removeItem(STORAGE_KEY);
158
+ }, []);
159
+ const ensureReady = useCallback(async () => {
160
+ const kit = kitRef.current;
161
+ if (!kit) return;
162
+ setLoading(true);
163
+ try {
164
+ await kit.ensureReady();
165
+ } catch (err) {
166
+ setError(err instanceof Error ? err : new Error(String(err)));
167
+ } finally {
168
+ setLoading(false);
169
+ }
170
+ }, []);
171
+ useEffect(() => {
172
+ if (!zapkit) return;
173
+ if (localStorage.getItem(STORAGE_KEY) !== "cartridge") return;
174
+ const originalError = console.error.bind(console);
175
+ console.error = (...args) => {
176
+ const msg = typeof args[0] === "string" ? args[0] : "";
177
+ if (msg.includes("Failed to fetch price for") || msg.includes("Insufficient liquidity in the routes")) return;
178
+ originalError(...args);
179
+ };
180
+ connect({ strategy: "cartridge" }, { isAutoReconnect: true }).finally(() => {
181
+ console.error = originalError;
182
+ });
183
+ }, [zapkit]);
184
+ const wallet = useMemo(() => ({
185
+ address,
186
+ ens,
187
+ balances,
188
+ status,
189
+ connect,
190
+ disconnect,
191
+ ensureReady,
192
+ loading,
193
+ error
194
+ }), [
195
+ address,
196
+ ens,
197
+ balances,
198
+ status,
199
+ connect,
200
+ disconnect,
201
+ ensureReady,
202
+ loading,
203
+ error
204
+ ]);
205
+ const value = useMemo(() => ({
206
+ zapkit,
207
+ wallet,
208
+ showDevPanel
209
+ }), [
210
+ zapkit,
211
+ wallet,
212
+ showDevPanel
213
+ ]);
214
+ return /* @__PURE__ */ jsxs(ZapContext.Provider, {
215
+ value,
216
+ children: [children, showDevPanel && /* @__PURE__ */ jsx(ZapDevPanel, {
217
+ error,
218
+ status,
219
+ address
220
+ })]
221
+ });
222
+ }
223
+ //#endregion
224
+ //#region src/hooks/useZapKit.ts
225
+ /** Returns the raw ZapKit instance. Prefer domain hooks (useWallet, useLending…) when possible. */
226
+ function useZapKit() {
227
+ return useZapContext().zapkit;
228
+ }
229
+ //#endregion
230
+ //#region src/hooks/useWallet.ts
231
+ /**
232
+ * Returns wallet state and actions: address, status, connect, disconnect, ensureReady, loading, error.
233
+ *
234
+ * @example
235
+ * const { address, connect, status } = useWallet();
236
+ */
237
+ function useWallet() {
238
+ return useZapContext().wallet;
239
+ }
240
+ //#endregion
241
+ //#region src/hooks/useLending.ts
242
+ /**
243
+ * Lending actions via the connected wallet's lending interface.
244
+ * Uses TanStack Query `useMutation` for loading/error state.
245
+ *
246
+ * @example
247
+ * const { deposit, withdraw, isPending, error } = useLending();
248
+ * await deposit({ token, amount });
249
+ */
250
+ function useLending() {
251
+ const { zapkit } = useZapContext();
252
+ const depositMutation = useMutation({ mutationFn: async (params) => {
253
+ const wallet = zapkit?.getWallet();
254
+ if (!wallet) throw new Error("Wallet not connected.");
255
+ const tx = await wallet.lending().deposit(params);
256
+ await tx.wait();
257
+ return tx;
258
+ } });
259
+ const withdrawMutation = useMutation({ mutationFn: async (params) => {
260
+ const wallet = zapkit?.getWallet();
261
+ if (!wallet) throw new Error("Wallet not connected.");
262
+ const tx = await wallet.lending().withdraw(params);
263
+ await tx.wait();
264
+ return tx;
265
+ } });
266
+ const isPending = depositMutation.isPending || withdrawMutation.isPending;
267
+ const error = depositMutation.error ?? withdrawMutation.error;
268
+ return {
269
+ deposit: depositMutation.mutateAsync,
270
+ withdraw: withdrawMutation.mutateAsync,
271
+ depositMutation,
272
+ withdrawMutation,
273
+ isPending,
274
+ error,
275
+ reset: () => {
276
+ depositMutation.reset();
277
+ withdrawMutation.reset();
278
+ }
279
+ };
280
+ }
281
+ //#endregion
282
+ //#region src/hooks/useSwap.ts
283
+ /**
284
+ * Swap action — currently a placeholder until ZapKit core exposes
285
+ * an AVNU/Ekubo swap shortcut. Powered by TanStack Query `useMutation`.
286
+ *
287
+ * @example
288
+ * const { swap, isPending, error } = useSwap();
289
+ * await swap(params);
290
+ */
291
+ function useSwap() {
292
+ const { zapkit } = useZapContext();
293
+ const swapMutation = useMutation({ mutationFn: async (_params) => {
294
+ if (!zapkit) throw new Error("Wallet not connected.");
295
+ throw new Error("Swap not yet available. Use wallet.execute() directly via useZapKit().");
296
+ } });
297
+ return {
298
+ swap: swapMutation.mutateAsync,
299
+ swapMutation,
300
+ isPending: swapMutation.isPending,
301
+ isError: swapMutation.isError,
302
+ error: swapMutation.error,
303
+ reset: swapMutation.reset
304
+ };
305
+ }
306
+ //#endregion
307
+ //#region src/hooks/useStaking.ts
308
+ /**
309
+ * Staking actions and pool data, powered by TanStack Query.
310
+ *
311
+ * @example
312
+ * const { stake, claimRewards, isPending, error } = useStaking();
313
+ * await stake({ poolAddress, amount });
314
+ */
315
+ function useStaking() {
316
+ const { zapkit } = useZapContext();
317
+ const address = (zapkit?.getWallet())?.account?.address;
318
+ const stakingTokensQuery = useQuery({
319
+ queryKey: ["zapkit", "stakingTokens"],
320
+ queryFn: () => zapkit.stakingTokens(),
321
+ enabled: !!zapkit
322
+ });
323
+ const stakerPoolsQuery = useQuery({
324
+ queryKey: [
325
+ "zapkit",
326
+ "stakerPools",
327
+ address
328
+ ],
329
+ queryFn: () => zapkit.getStakerPools(address),
330
+ enabled: !!zapkit && !!address
331
+ });
332
+ const stakeMutation = useMutation({ mutationFn: (params) => {
333
+ if (!zapkit) throw new Error("Wallet not connected.");
334
+ return zapkit.stake(params);
335
+ } });
336
+ const claimMutation = useMutation({ mutationFn: (poolAddress) => {
337
+ if (!zapkit) throw new Error("Wallet not connected.");
338
+ return zapkit.claimRewards(poolAddress);
339
+ } });
340
+ const isPending = stakeMutation.isPending || claimMutation.isPending;
341
+ const error = stakeMutation.error ?? claimMutation.error;
342
+ return {
343
+ stake: stakeMutation.mutateAsync,
344
+ claimRewards: claimMutation.mutateAsync,
345
+ stakeMutation,
346
+ claimMutation,
347
+ isPending,
348
+ error,
349
+ reset: () => {
350
+ stakeMutation.reset();
351
+ claimMutation.reset();
352
+ },
353
+ stakingTokens: stakingTokensQuery.data,
354
+ stakerPools: stakerPoolsQuery.data,
355
+ isLoading: stakingTokensQuery.isLoading || stakerPoolsQuery.isLoading
356
+ };
357
+ }
358
+ //#endregion
359
+ //#region src/hooks/useBalance.ts
360
+ /**
361
+ * Fetches a token balance for the connected wallet.
362
+ * Refreshes every 30 s while mounted.
363
+ *
364
+ * @example
365
+ * const { data: balance, isLoading } = useBalance("STRK");
366
+ */
367
+ function useBalance(token) {
368
+ const { zapkit } = useZapContext();
369
+ return useQuery({
370
+ queryKey: [
371
+ "zapkit",
372
+ "balance",
373
+ token
374
+ ],
375
+ queryFn: () => zapkit.getBalance(token),
376
+ enabled: !!zapkit,
377
+ refetchInterval: 3e4
378
+ });
379
+ }
380
+ //#endregion
381
+ //#region src/hooks/useBridgingTokens.ts
382
+ /**
383
+ * Fetches the list of bridging tokens via TanStack Query.
384
+ *
385
+ * @example
386
+ * const { data: tokens, isLoading } = useBridgingTokens();
387
+ * const { data: ethTokens } = useBridgingTokens("ethereum");
388
+ */
389
+ function useBridgingTokens(chain) {
390
+ const { zapkit } = useZapContext();
391
+ return useQuery({
392
+ queryKey: [
393
+ "zapkit",
394
+ "bridgingTokens",
395
+ chain
396
+ ],
397
+ queryFn: () => zapkit.getBridgingTokens(chain),
398
+ enabled: !!zapkit,
399
+ staleTime: 6e4
400
+ });
401
+ }
402
+ //#endregion
403
+ //#region src/hooks/useStakingPools.ts
404
+ /**
405
+ * Fetches staking token list and pools for a given staker address.
406
+ *
407
+ * @example
408
+ * const { pools, stakingTokens, isLoading } = useStakingPools("0x049d36…");
409
+ * // Or use the connected wallet address automatically:
410
+ * const { pools, stakingTokens } = useStakingPools();
411
+ */
412
+ function useStakingPools(stakerAddress) {
413
+ const { zapkit, wallet } = useZapContext();
414
+ const resolvedAddress = stakerAddress ?? wallet.address;
415
+ const stakingTokensQuery = useQuery({
416
+ queryKey: ["zapkit", "stakingTokens"],
417
+ queryFn: () => zapkit.stakingTokens(),
418
+ enabled: !!zapkit,
419
+ staleTime: 6e4
420
+ });
421
+ const stakerPoolsQuery = useQuery({
422
+ queryKey: [
423
+ "zapkit",
424
+ "stakerPools",
425
+ resolvedAddress
426
+ ],
427
+ queryFn: () => zapkit.getStakerPools(resolvedAddress),
428
+ enabled: !!zapkit && !!resolvedAddress,
429
+ staleTime: 3e4
430
+ });
431
+ return {
432
+ stakingTokens: stakingTokensQuery.data,
433
+ pools: stakerPoolsQuery.data,
434
+ isLoading: stakingTokensQuery.isLoading || stakerPoolsQuery.isLoading,
435
+ isError: stakingTokensQuery.isError || stakerPoolsQuery.isError,
436
+ error: stakingTokensQuery.error ?? stakerPoolsQuery.error,
437
+ stakingTokensQuery,
438
+ stakerPoolsQuery
439
+ };
440
+ }
441
+ //#endregion
442
+ //#region src/queryClient.ts
443
+ /**
444
+ * Creates a pre-configured `QueryClient` suitable for ZapKit blockchain queries.
445
+ *
446
+ * Sensible defaults:
447
+ * - `staleTime`: 30 s (blocks don't update instantly)
448
+ * - `gcTime`: 5 min
449
+ * - `retry`: 2
450
+ * - `refetchOnWindowFocus`: false (avoids noisy refetches on tab switch)
451
+ *
452
+ * Pass to `<QueryClientProvider>` when you want to share a single client
453
+ * across your app AND ZapKit:
454
+ *
455
+ * @example
456
+ * const queryClient = createZapQueryClient();
457
+ *
458
+ * <QueryClientProvider client={queryClient}>
459
+ * <ZapProvider config={config} queryClient={queryClient}>
460
+ * <App />
461
+ * </ZapProvider>
462
+ * </QueryClientProvider>
463
+ */
464
+ function createZapQueryClient() {
465
+ return new QueryClient({ defaultOptions: { queries: {
466
+ staleTime: 3e4,
467
+ gcTime: 5 * 6e4,
468
+ retry: 2,
469
+ refetchOnWindowFocus: false
470
+ } } });
471
+ }
472
+ //#endregion
473
+ export { Amount, BridgeToken, ChainId, ExternalChain, OnboardStrategy, QueryClient, StarkSigner, Tx, TxBuilder, ZapContext, ZapDevPanel, ZapProvider, accountPresets, createZapQueryClient, fromAddress, getPresets, useBalance, useBridgingTokens, useLending, useStaking, useStakingPools, useSwap, useWallet, useZapContext, useZapKit };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@dngbuilds/zapkit-react",
3
+ "version": "0.0.1",
4
+ "description": "Zapkit React package.",
5
+ "homepage": "",
6
+ "bugs": {
7
+ "url": ""
8
+ },
9
+ "license": "MIT",
10
+ "author": "DngBuilds <dng.builds@gmail.com>",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/author/library.git"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "exports": {
20
+ ".": "./dist/index.mjs",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "vp pack",
28
+ "dev": "vp pack --watch",
29
+ "test": "vp test",
30
+ "check": "vp check",
31
+ "prepublishOnly": "vp run build"
32
+ },
33
+ "dependencies": {
34
+ "@tanstack/react-query": "catalog:",
35
+ "@dngbuilds/zapkit-core": "workspace:*",
36
+ "react": "catalog:",
37
+ "react-dom": "catalog:"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.5.0",
41
+ "@types/react": "catalog:",
42
+ "@typescript/native-preview": "7.0.0-dev.20260316.1",
43
+ "bumpp": "^11.0.1",
44
+ "typescript": "^5.9.3",
45
+ "vite-plus": "^0.1.11"
46
+ },
47
+ "inlinedDependencies": {
48
+ "@noble/curves": "1.7.0",
49
+ "@starknet-io/types-js": [
50
+ "0.10.0",
51
+ "0.9.2"
52
+ ],
53
+ "starknet": "9.4.2",
54
+ "starkzap": "2.0.0",
55
+ "ts-mixer": "6.0.4"
56
+ }
57
+ }