@hardkas/react 0.4.0-alpha → 0.5.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ React hooks and provider context for integrating with the HardKAS deterministic
4
4
 
5
5
  ## ⚠️ Alpha-Only Local-Runtime Coupling Warning
6
6
 
7
- Please note that in the `v0.4.0-alpha` release, this package has direct dependencies on the **`@hardkas/bridge-local`** package to facilitate local bridge payload planning and prefix-mining simulations directly within hooks like `useBridgeLocalPlan` and `useBridgeLocalSimulation`.
7
+ Please note that in the `v0.5.0-alpha` release, this package has direct dependencies on the **`@hardkas/bridge-local`** package to facilitate local bridge payload planning and prefix-mining simulations directly within hooks like `useBridgeLocalPlan` and `useBridgeLocalSimulation`.
8
8
 
9
9
  ### 🛡️ Production & Browser Safety
10
10
 
package/dist/index.d.ts CHANGED
@@ -6,6 +6,16 @@ import * as viem from 'viem';
6
6
  import { PublicClient, Address } from 'viem';
7
7
  import { Hex } from '../types/misc.js';
8
8
 
9
+ interface EIP6963ProviderInfo {
10
+ uuid: string;
11
+ name: string;
12
+ icon: string;
13
+ rdns: string;
14
+ }
15
+ interface EIP6963ProviderDetail {
16
+ info: EIP6963ProviderInfo;
17
+ provider: any;
18
+ }
9
19
  interface HardKasReactConfig {
10
20
  readonly kaspaRpcUrl?: string;
11
21
  readonly igraRpcUrl?: string;
@@ -28,6 +38,13 @@ interface HardKasContextValue {
28
38
  readonly sseStatus: SSEStatus;
29
39
  readonly lastEvent: RuntimeEvent | null;
30
40
  readonly subscribe: (callback: EventCallback) => () => void;
41
+ readonly providers: EIP6963ProviderDetail[];
42
+ readonly activeProvider: EIP6963ProviderDetail | null;
43
+ readonly walletAddress: string | null;
44
+ readonly walletChainId: number | null;
45
+ readonly connectWallet: (detail: EIP6963ProviderDetail) => Promise<void>;
46
+ readonly disconnectWallet: () => void;
47
+ readonly switchChain: (chainId: number) => Promise<void>;
31
48
  }
32
49
  interface HardKasProviderProps {
33
50
  readonly config: HardKasReactConfig;
@@ -89,8 +106,19 @@ declare function useKaspaBalance(options?: {
89
106
  declare function useIgraAccount(): {
90
107
  name: string | undefined;
91
108
  address: `0x${string}` | undefined;
109
+ isWallet: boolean;
92
110
  isLoading: boolean;
93
111
  };
112
+ declare function useIgraWallet(): {
113
+ providers: EIP6963ProviderDetail[];
114
+ activeProvider: EIP6963ProviderDetail | null;
115
+ walletAddress: string | null;
116
+ walletChainId: number | null;
117
+ connectWallet: (detail: EIP6963ProviderDetail) => Promise<void>;
118
+ disconnectWallet: () => void;
119
+ switchChain: (chainId: number) => Promise<void>;
120
+ isConnected: boolean;
121
+ };
94
122
  declare function useIgraBalance(options?: {
95
123
  refetchInterval?: number;
96
124
  }): _tanstack_react_query.UseQueryResult<bigint, Error>;
@@ -4719,4 +4747,4 @@ declare function useIgraWriteContract(): _tanstack_react_query.UseMutationResult
4719
4747
  }, unknown>;
4720
4748
  declare function useIgraWaitForReceipt(): _tanstack_react_query.UseMutationResult<viem.TransactionReceipt, Error, `0x${string}`, unknown>;
4721
4749
 
4722
- export { type EventCallback, type HardKasContextValue, HardKasProvider, type HardKasReactConfig, type HealthInfo, type KasWareLocalState, type KasWareSessionMatch, type MetaMaskLocalState, type RuntimeEvent, type SSEStatus, type SandboxConnection, type SessionInfo, useBridgeLocalPlan, useBridgeLocalSimulation, useConnectKasWareLocal, useCreateSandboxSession, useDisconnectSandboxSession, useHardKas, useHardKasHealth, useHardKasSession, useIgraAccount, useIgraBalance, useIgraInjectedAccount, useIgraReadContract, useIgraWaitForReceipt, useIgraWriteContract, useKasWareLocal, useKasWareSessionMatch, useKaspaBalance, useKaspaWallet, useLocalIgraWalletClient, useMetaMaskLocal, usePairSandboxSession, useSandboxSessions, useSwitchToLocalIgra };
4750
+ export { type EventCallback, type HardKasContextValue, HardKasProvider, type HardKasReactConfig, type HealthInfo, type KasWareLocalState, type KasWareSessionMatch, type MetaMaskLocalState, type RuntimeEvent, type SSEStatus, type SandboxConnection, type SessionInfo, useBridgeLocalPlan, useBridgeLocalSimulation, useConnectKasWareLocal, useCreateSandboxSession, useDisconnectSandboxSession, useHardKas, useHardKasHealth, useHardKasSession, useIgraAccount, useIgraBalance, useIgraInjectedAccount, useIgraReadContract, useIgraWaitForReceipt, useIgraWallet, useIgraWriteContract, useKasWareLocal, useKasWareSessionMatch, useKaspaBalance, useKaspaWallet, useLocalIgraWalletClient, useMetaMaskLocal, usePairSandboxSession, useSandboxSessions, useSwitchToLocalIgra };
package/dist/index.js CHANGED
@@ -12,6 +12,10 @@ function HardKasProvider({ config, children, queryClient: externalQueryClient })
12
12
  const eventSource = React.useRef(null);
13
13
  const reconnectTimer = React.useRef(null);
14
14
  const backoffMs = React.useRef(500);
15
+ const [providers, setProviders] = React.useState([]);
16
+ const [activeProvider, setActiveProvider] = React.useState(null);
17
+ const [walletAddress, setWalletAddress] = React.useState(null);
18
+ const [walletChainId, setWalletChainId] = React.useState(null);
15
19
  const subscribe = React.useCallback((callback) => {
16
20
  listeners.current.add(callback);
17
21
  return () => listeners.current.delete(callback);
@@ -91,6 +95,135 @@ function HardKasProvider({ config, children, queryClient: externalQueryClient })
91
95
  }
92
96
  };
93
97
  }, [connect]);
98
+ React.useEffect(() => {
99
+ if (typeof window === "undefined") return;
100
+ const handleAnnounce = (event) => {
101
+ const detail = event.detail;
102
+ if (!detail || !detail.info || !detail.provider) return;
103
+ setProviders((prev) => {
104
+ if (prev.some((p) => p.info.rdns === detail.info.rdns)) return prev;
105
+ return [...prev, detail];
106
+ });
107
+ };
108
+ window.addEventListener("eip6963:announceProvider", handleAnnounce);
109
+ window.dispatchEvent(new Event("eip6963:requestProvider"));
110
+ return () => {
111
+ window.removeEventListener("eip6963:announceProvider", handleAnnounce);
112
+ };
113
+ }, []);
114
+ const connectWallet = React.useCallback(async (detail) => {
115
+ try {
116
+ const accounts = await detail.provider.request({ method: "eth_requestAccounts" });
117
+ const chainIdHex = await detail.provider.request({ method: "eth_chainId" });
118
+ const chainId = typeof chainIdHex === "string" ? parseInt(chainIdHex, 16) : Number(chainIdHex);
119
+ setActiveProvider(detail);
120
+ if (accounts && accounts[0]) {
121
+ setWalletAddress(accounts[0]);
122
+ }
123
+ setWalletChainId(chainId);
124
+ if (typeof window !== "undefined") {
125
+ window.localStorage.setItem("hardkas:active-wallet", detail.info.rdns);
126
+ }
127
+ } catch (err) {
128
+ console.error("Failed to connect wallet:", err);
129
+ throw err;
130
+ }
131
+ }, []);
132
+ const disconnectWallet = React.useCallback(() => {
133
+ setActiveProvider(null);
134
+ setWalletAddress(null);
135
+ setWalletChainId(null);
136
+ if (typeof window !== "undefined") {
137
+ window.localStorage.removeItem("hardkas:active-wallet");
138
+ }
139
+ }, []);
140
+ const switchChain = React.useCallback(async (targetChainId) => {
141
+ if (!activeProvider) {
142
+ throw new Error("No active wallet connected");
143
+ }
144
+ const hexChainId = `0x${targetChainId.toString(16)}`;
145
+ try {
146
+ await activeProvider.provider.request({
147
+ method: "wallet_switchEthereumChain",
148
+ params: [{ chainId: hexChainId }]
149
+ });
150
+ setWalletChainId(targetChainId);
151
+ } catch (err) {
152
+ if (err.code === 4902) {
153
+ if (targetChainId === 19416) {
154
+ await activeProvider.provider.request({
155
+ method: "wallet_addEthereumChain",
156
+ params: [{
157
+ chainId: hexChainId,
158
+ chainName: "Igra Local",
159
+ nativeCurrency: { name: "Igra Kaspa", symbol: "iKAS", decimals: 18 },
160
+ rpcUrls: [config.igraRpcUrl || "http://127.0.0.1:8545"]
161
+ }]
162
+ });
163
+ setWalletChainId(targetChainId);
164
+ } else {
165
+ throw err;
166
+ }
167
+ } else {
168
+ throw err;
169
+ }
170
+ }
171
+ }, [activeProvider, config.igraRpcUrl]);
172
+ React.useEffect(() => {
173
+ if (typeof window === "undefined" || providers.length === 0 || activeProvider) return;
174
+ const savedRdns = window.localStorage.getItem("hardkas:active-wallet");
175
+ if (savedRdns) {
176
+ const match = providers.find((p) => p.info.rdns === savedRdns);
177
+ if (match) {
178
+ connectWallet(match).catch(() => {
179
+ });
180
+ }
181
+ }
182
+ }, [providers, activeProvider, connectWallet]);
183
+ React.useEffect(() => {
184
+ if (!activeProvider) {
185
+ setWalletAddress(null);
186
+ setWalletChainId(null);
187
+ return;
188
+ }
189
+ const provider = activeProvider.provider;
190
+ const handleAccountsChanged = (accounts) => {
191
+ if (accounts && accounts[0]) {
192
+ setWalletAddress(accounts[0]);
193
+ } else {
194
+ setActiveProvider(null);
195
+ setWalletAddress(null);
196
+ setWalletChainId(null);
197
+ if (typeof window !== "undefined") {
198
+ window.localStorage.removeItem("hardkas:active-wallet");
199
+ }
200
+ }
201
+ };
202
+ const handleChainChanged = (chainIdHex) => {
203
+ const chainId = typeof chainIdHex === "string" ? parseInt(chainIdHex, 16) : Number(chainIdHex);
204
+ setWalletChainId(chainId);
205
+ };
206
+ const handleDisconnect = () => {
207
+ setActiveProvider(null);
208
+ setWalletAddress(null);
209
+ setWalletChainId(null);
210
+ if (typeof window !== "undefined") {
211
+ window.localStorage.removeItem("hardkas:active-wallet");
212
+ }
213
+ };
214
+ if (provider.on) {
215
+ provider.on("accountsChanged", handleAccountsChanged);
216
+ provider.on("chainChanged", handleChainChanged);
217
+ provider.on("disconnect", handleDisconnect);
218
+ }
219
+ return () => {
220
+ if (provider.removeListener) {
221
+ provider.removeListener("accountsChanged", handleAccountsChanged);
222
+ provider.removeListener("chainChanged", handleChainChanged);
223
+ provider.removeListener("disconnect", handleDisconnect);
224
+ }
225
+ };
226
+ }, [activeProvider]);
94
227
  const igraClient = useMemo(() => {
95
228
  return createPublicClient({
96
229
  chain: {
@@ -114,8 +247,29 @@ function HardKasProvider({ config, children, queryClient: externalQueryClient })
114
247
  queryClient,
115
248
  sseStatus,
116
249
  lastEvent,
117
- subscribe
118
- }), [config, igraClient, queryClient, sseStatus, lastEvent, subscribe]);
250
+ subscribe,
251
+ providers,
252
+ activeProvider,
253
+ walletAddress,
254
+ walletChainId,
255
+ connectWallet,
256
+ disconnectWallet,
257
+ switchChain
258
+ }), [
259
+ config,
260
+ igraClient,
261
+ queryClient,
262
+ sseStatus,
263
+ lastEvent,
264
+ subscribe,
265
+ providers,
266
+ activeProvider,
267
+ walletAddress,
268
+ walletChainId,
269
+ connectWallet,
270
+ disconnectWallet,
271
+ switchChain
272
+ ]);
119
273
  return /* @__PURE__ */ jsx(HardKasContext.Provider, { value, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children }) });
120
274
  }
121
275
  function useHardKas() {
@@ -250,10 +404,34 @@ function useKaspaBalance(options = {}) {
250
404
  import { useQuery as useQuery4 } from "@tanstack/react-query";
251
405
  function useIgraAccount() {
252
406
  const { data: session } = useHardKasSession();
407
+ const { walletAddress } = useHardKas();
408
+ const address = walletAddress || session?.l2.address;
253
409
  return {
254
- name: session?.l2.account,
255
- address: session?.l2.address,
256
- isLoading: !session
410
+ name: walletAddress ? "Browser Wallet" : session?.l2.account,
411
+ address,
412
+ isWallet: !!walletAddress,
413
+ isLoading: !session && !walletAddress
414
+ };
415
+ }
416
+ function useIgraWallet() {
417
+ const {
418
+ providers,
419
+ activeProvider,
420
+ walletAddress,
421
+ walletChainId,
422
+ connectWallet,
423
+ disconnectWallet,
424
+ switchChain
425
+ } = useHardKas();
426
+ return {
427
+ providers,
428
+ activeProvider,
429
+ walletAddress,
430
+ walletChainId,
431
+ connectWallet,
432
+ disconnectWallet,
433
+ switchChain,
434
+ isConnected: !!walletAddress
257
435
  };
258
436
  }
259
437
  function useIgraBalance(options = {}) {
@@ -587,6 +765,7 @@ function useDisconnectSandboxSession() {
587
765
 
588
766
  // src/hooks/contracts.ts
589
767
  import { useQuery as useQuery7, useMutation as useMutation3 } from "@tanstack/react-query";
768
+ import { createWalletClient as createWalletClient2, custom as custom2 } from "viem";
590
769
  function useIgraReadContract(options) {
591
770
  const { igraClient, config } = useHardKas();
592
771
  const { data: session } = useHardKasSession();
@@ -605,12 +784,24 @@ function useIgraReadContract(options) {
605
784
  });
606
785
  }
607
786
  function useIgraWriteContract() {
787
+ const { activeProvider, walletAddress, igraClient } = useHardKas();
608
788
  return useMutation3({
609
789
  mutationFn: async (params) => {
610
- if (!params.walletClient) {
611
- throw new Error("A wallet client is required to write to a contract.");
790
+ let client = params.walletClient;
791
+ if (!client) {
792
+ if (!activeProvider) {
793
+ throw new Error("No active browser wallet connected and no walletClient was provided.");
794
+ }
795
+ if (!walletAddress) {
796
+ throw new Error("No active account address available on connected wallet.");
797
+ }
798
+ client = createWalletClient2({
799
+ account: walletAddress,
800
+ chain: igraClient.chain,
801
+ transport: custom2(activeProvider.provider)
802
+ });
612
803
  }
613
- return await params.walletClient.writeContract({
804
+ return await client.writeContract({
614
805
  address: params.address,
615
806
  abi: params.abi,
616
807
  functionName: params.functionName,
@@ -642,6 +833,7 @@ export {
642
833
  useIgraInjectedAccount,
643
834
  useIgraReadContract,
644
835
  useIgraWaitForReceipt,
836
+ useIgraWallet,
645
837
  useIgraWriteContract,
646
838
  useKasWareLocal,
647
839
  useKasWareSessionMatch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/react",
3
- "version": "0.4.0-alpha",
3
+ "version": "0.5.0-alpha",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,11 +14,11 @@
14
14
  "dependencies": {
15
15
  "@tanstack/react-query": "^5.61.5",
16
16
  "viem": "^2.21.51",
17
- "@hardkas/bridge-local": "0.4.0-alpha",
18
- "@hardkas/core": "0.4.0-alpha",
19
- "@hardkas/kaspa-rpc": "0.4.0-alpha",
20
- "@hardkas/sessions": "0.4.0-alpha",
21
- "@hardkas/l2": "0.4.0-alpha"
17
+ "@hardkas/core": "0.5.0-alpha",
18
+ "@hardkas/bridge-local": "0.5.0-alpha",
19
+ "@hardkas/sessions": "0.5.0-alpha",
20
+ "@hardkas/l2": "0.5.0-alpha",
21
+ "@hardkas/kaspa-rpc": "0.5.0-alpha"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@testing-library/dom": "^10.4.1",