@jaw.id/wagmi 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.
@@ -0,0 +1,517 @@
1
+ 'use client';
2
+ import { useEffect, useMemo, useRef } from 'react';
3
+ import { useAccount, useChainId, useConfig, useConnectors, } from 'wagmi';
4
+ import { useMutation, useQuery, useQueryClient, skipToken, } from '@tanstack/react-query';
5
+ import { connect, disconnect, grantPermissions, getPermissions, revokePermissions, getAssets, getCapabilities, sign, getCallsHistory, } from './core.js';
6
+ import { getPermissionsQueryKey, getAssetsQueryKey, getCapabilitiesQueryKey, getCallsHistoryQueryKey } from './query.js';
7
+ /**
8
+ * Hook to connect to the wallet with optional capabilities.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const { mutate, data, isPending } = useConnect();
13
+ *
14
+ * // Basic connect
15
+ * mutate({ connector: jawWallet({ apiKey: 'xxx' }) });
16
+ *
17
+ * // Connect with capabilities (subname issuance)
18
+ * mutate({
19
+ * connector: jawWallet({ apiKey: 'xxx' }),
20
+ * capabilities: {
21
+ * subnameTextRecords: [
22
+ * { key: 'avatar', value: 'https://example.com/avatar.png' },
23
+ * ],
24
+ * },
25
+ * });
26
+ * ```
27
+ */
28
+ export function useConnect(parameters = {}) {
29
+ const { mutation } = parameters;
30
+ const config = useConfig(parameters);
31
+ return useMutation({
32
+ ...mutation,
33
+ mutationFn: async (variables) => {
34
+ return connect(config, variables);
35
+ },
36
+ mutationKey: ['connect'],
37
+ });
38
+ }
39
+ /**
40
+ * Hook to grant permissions to a spender address.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { mutate, data, isPending } = useGrantPermissions();
45
+ *
46
+ * mutate({
47
+ * expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour
48
+ * spender: '0x...',
49
+ * permissions: {
50
+ * calls: [{ target: '0x...', functionSignature: 'transfer(address,uint256)' }],
51
+ * spends: [{ token: '0x...', allowance: '1000000000000000000', unit: 'hour', multiplier: 1 }],
52
+ * },
53
+ * });
54
+ * ```
55
+ */
56
+ export function useGrantPermissions(parameters = {}) {
57
+ const { mutation } = parameters;
58
+ const config = useConfig(parameters);
59
+ return useMutation({
60
+ ...mutation,
61
+ mutationFn: async (variables) => {
62
+ return grantPermissions(config, variables);
63
+ },
64
+ mutationKey: ['grantPermissions'],
65
+ });
66
+ }
67
+ /**
68
+ * Hook to revoke a permission by its ID.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * const { mutate, isPending } = useRevokePermissions();
73
+ *
74
+ * mutate({
75
+ * id: '0x...', // permission hash
76
+ * });
77
+ * ```
78
+ */
79
+ export function useRevokePermissions(parameters = {}) {
80
+ const { mutation } = parameters;
81
+ const config = useConfig(parameters);
82
+ return useMutation({
83
+ ...mutation,
84
+ mutationFn: async (variables) => {
85
+ return revokePermissions(config, variables);
86
+ },
87
+ mutationKey: ['revokePermissions'],
88
+ });
89
+ }
90
+ /**
91
+ * Hook to get the current permissions for an account.
92
+ * Automatically updates when permissions change.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * const { data: permissions, isLoading } = usePermissions();
97
+ *
98
+ * // With specific address (works even when not connected)
99
+ * const { data } = usePermissions({ address: '0x...' });
100
+ * ```
101
+ */
102
+ export function usePermissions(parameters = {}) {
103
+ const { query = {}, ...rest } = parameters;
104
+ const config = useConfig(rest);
105
+ const queryClient = useQueryClient();
106
+ const chainId = useChainId({ config });
107
+ const { address: connectedAddress, connector, status } = useAccount({ config });
108
+ const connectors = useConnectors({ config });
109
+ // Use account connector if connected, otherwise find JAW connector from available connectors
110
+ const jawConnector = connectors.find((c) => c.id === 'jaw');
111
+ const activeConnector = parameters.connector ?? connector ?? jawConnector;
112
+ // Use explicit address if provided, otherwise fall back to connected address
113
+ const targetAddress = parameters.address ?? connectedAddress;
114
+ // Enable query if:
115
+ // 1. Connected (existing behavior), OR
116
+ // 2. Explicit address provided AND connector available (disconnected query)
117
+ const isConnected = status === 'connected' || (status === 'reconnecting' && activeConnector?.getProvider);
118
+ const canQueryDisconnected = Boolean(targetAddress && activeConnector?.getProvider);
119
+ const enabled = Boolean((isConnected || canQueryDisconnected) && (query.enabled ?? true));
120
+ const queryKey = useMemo(() => getPermissionsQueryKey({
121
+ address: targetAddress,
122
+ chainId: parameters.chainId ?? chainId,
123
+ connector: activeConnector,
124
+ }), [targetAddress, chainId, parameters.chainId, activeConnector]);
125
+ // Set up event listener for permission changes
126
+ const providerRef = useRef(undefined);
127
+ const handlerRef = useRef(undefined);
128
+ useEffect(() => {
129
+ if (!activeConnector)
130
+ return;
131
+ let mounted = true;
132
+ void (async () => {
133
+ const provider = (await activeConnector.getProvider?.());
134
+ if (!mounted || !provider)
135
+ return;
136
+ providerRef.current = provider;
137
+ const handleMessage = (event) => {
138
+ if (event.type !== 'permissionsChanged')
139
+ return;
140
+ queryClient.invalidateQueries({ queryKey });
141
+ };
142
+ handlerRef.current = handleMessage;
143
+ provider.on('message', handleMessage);
144
+ })();
145
+ return () => {
146
+ mounted = false;
147
+ if (providerRef.current && handlerRef.current) {
148
+ providerRef.current.removeListener?.('message', handlerRef.current);
149
+ }
150
+ };
151
+ }, [activeConnector, queryClient, queryKey]);
152
+ return useQuery({
153
+ ...query,
154
+ enabled,
155
+ gcTime: 0,
156
+ queryFn: activeConnector
157
+ ? async () => {
158
+ // When connected, use the standard flow
159
+ if (isConnected) {
160
+ return getPermissions(config, {
161
+ ...rest,
162
+ connector: activeConnector,
163
+ });
164
+ }
165
+ // When disconnected but have address, make direct provider call
166
+ const provider = (await activeConnector.getProvider?.());
167
+ if (!provider)
168
+ throw new Error('Provider not available');
169
+ if (!targetAddress)
170
+ throw new Error('Address is required when not connected');
171
+ return provider.request({
172
+ method: 'wallet_getPermissions',
173
+ params: [{ address: targetAddress }],
174
+ });
175
+ }
176
+ : skipToken,
177
+ queryKey,
178
+ staleTime: Number.POSITIVE_INFINITY,
179
+ });
180
+ }
181
+ /**
182
+ * Hook to disconnect from the wallet.
183
+ *
184
+ * @example
185
+ * ```tsx
186
+ * const { mutate: disconnectWallet, isPending } = useDisconnect();
187
+ *
188
+ * // Disconnect active connector
189
+ * disconnectWallet({});
190
+ *
191
+ * // Disconnect specific connector
192
+ * disconnectWallet({ connector: jawWallet({ apiKey: 'xxx' }) });
193
+ * ```
194
+ */
195
+ export function useDisconnect(parameters = {}) {
196
+ const { mutation } = parameters;
197
+ const config = useConfig(parameters);
198
+ return useMutation({
199
+ ...mutation,
200
+ mutationFn: async (variables) => {
201
+ return disconnect(config, variables);
202
+ },
203
+ mutationKey: ['disconnect'],
204
+ });
205
+ }
206
+ /**
207
+ * Hook to get the assets for an account.
208
+ * Automatically updates when assets change.
209
+ *
210
+ * @example
211
+ * ```tsx
212
+ * const { data: assets, isLoading } = useGetAssets();
213
+ *
214
+ * // With specific address (works even when not connected)
215
+ * const { data } = useGetAssets({ address: '0x...' });
216
+ *
217
+ * // With chain filter
218
+ * const { data } = useGetAssets({ chainFilter: ['0x1', '0xa'] });
219
+ *
220
+ * // With asset type filter
221
+ * const { data } = useGetAssets({ assetTypeFilter: ['erc20'] });
222
+ * ```
223
+ */
224
+ export function useGetAssets(parameters = {}) {
225
+ const { query = {}, ...rest } = parameters;
226
+ const config = useConfig(rest);
227
+ const queryClient = useQueryClient();
228
+ const chainId = useChainId({ config });
229
+ const { address: connectedAddress, connector, status } = useAccount({ config });
230
+ const connectors = useConnectors({ config });
231
+ // Use account connector if connected, otherwise find JAW connector from available connectors
232
+ const jawConnector = connectors.find((c) => c.id === 'jaw');
233
+ const activeConnector = parameters.connector ?? connector ?? jawConnector;
234
+ // Use explicit address if provided, otherwise fall back to connected address
235
+ const targetAddress = parameters.address ?? connectedAddress;
236
+ // Enable query if:
237
+ // 1. Connected (existing behavior), OR
238
+ // 2. Explicit address provided AND connector available (disconnected query)
239
+ const isConnected = status === 'connected' || (status === 'reconnecting' && activeConnector?.getProvider);
240
+ const canQueryDisconnected = Boolean(targetAddress && activeConnector?.getProvider);
241
+ const enabled = Boolean((isConnected || canQueryDisconnected) && (query.enabled ?? true));
242
+ const queryKey = useMemo(() => getAssetsQueryKey({
243
+ address: targetAddress,
244
+ chainId: parameters.chainId ?? chainId,
245
+ connector: activeConnector,
246
+ chainFilter: parameters.chainFilter,
247
+ assetTypeFilter: parameters.assetTypeFilter,
248
+ assetFilter: parameters.assetFilter,
249
+ }), [
250
+ targetAddress,
251
+ chainId,
252
+ parameters.chainId,
253
+ activeConnector,
254
+ parameters.chainFilter,
255
+ parameters.assetTypeFilter,
256
+ parameters.assetFilter,
257
+ ]);
258
+ // Set up event listener for asset changes (e.g., after transactions)
259
+ const providerRef = useRef(undefined);
260
+ const handlerRef = useRef(undefined);
261
+ useEffect(() => {
262
+ if (!activeConnector)
263
+ return;
264
+ let mounted = true;
265
+ void (async () => {
266
+ const provider = (await activeConnector.getProvider?.());
267
+ if (!mounted || !provider)
268
+ return;
269
+ providerRef.current = provider;
270
+ const handleMessage = (event) => {
271
+ if (event.type !== 'assetsChanged')
272
+ return;
273
+ queryClient.invalidateQueries({ queryKey });
274
+ };
275
+ handlerRef.current = handleMessage;
276
+ provider.on('message', handleMessage);
277
+ })();
278
+ return () => {
279
+ mounted = false;
280
+ if (providerRef.current && handlerRef.current) {
281
+ providerRef.current.removeListener?.('message', handlerRef.current);
282
+ }
283
+ };
284
+ }, [activeConnector, queryClient, queryKey]);
285
+ return useQuery({
286
+ ...query,
287
+ enabled,
288
+ gcTime: 0,
289
+ queryFn: activeConnector
290
+ ? async () => {
291
+ // When connected, use the standard flow
292
+ if (isConnected) {
293
+ return getAssets(config, {
294
+ ...rest,
295
+ address: targetAddress,
296
+ connector: activeConnector,
297
+ });
298
+ }
299
+ // When disconnected but have address, make direct provider call
300
+ const provider = (await activeConnector.getProvider?.());
301
+ if (!provider)
302
+ throw new Error('Provider not available');
303
+ if (!targetAddress)
304
+ throw new Error('Address is required when not connected');
305
+ return provider.request({
306
+ method: 'wallet_getAssets',
307
+ params: [{
308
+ account: targetAddress,
309
+ chainFilter: parameters.chainFilter,
310
+ assetTypeFilter: parameters.assetTypeFilter,
311
+ assetFilter: parameters.assetFilter,
312
+ }],
313
+ });
314
+ }
315
+ : skipToken,
316
+ queryKey,
317
+ staleTime: 30_000, // Cache for 30 seconds since assets change less frequently
318
+ });
319
+ }
320
+ /**
321
+ * Hook to get the wallet capabilities (EIP-5792).
322
+ * Can be called without a connected account.
323
+ *
324
+ * @example
325
+ * ```tsx
326
+ * // Get capabilities (uses connected account if available)
327
+ * const { data: capabilities, isLoading } = useCapabilities();
328
+ *
329
+ * // With specific address (works even when not connected)
330
+ * const { data } = useCapabilities({ address: '0x...' });
331
+ *
332
+ * // With chain filter
333
+ * const { data } = useCapabilities({ chainFilter: ['0x1', '0xa'] });
334
+ * ```
335
+ */
336
+ export function useCapabilities(parameters = {}) {
337
+ const { query = {}, ...rest } = parameters;
338
+ const config = useConfig(rest);
339
+ const chainId = useChainId({ config });
340
+ const { address: connectedAddress, connector, status } = useAccount({ config });
341
+ const connectors = useConnectors({ config });
342
+ // Use account connector if connected, otherwise find JAW connector from available connectors
343
+ const jawConnector = connectors.find((c) => c.id === 'jaw');
344
+ const activeConnector = parameters.connector ?? connector ?? jawConnector;
345
+ // Use explicit address if provided, otherwise fall back to connected address
346
+ const targetAddress = parameters.address ?? connectedAddress;
347
+ // Enable query if:
348
+ // 1. Connected (existing behavior), OR
349
+ // 2. Connector available (can query capabilities without connection)
350
+ const isConnected = status === 'connected' || (status === 'reconnecting' && activeConnector?.getProvider);
351
+ const canQueryDisconnected = Boolean(activeConnector?.getProvider);
352
+ const enabled = Boolean((isConnected || canQueryDisconnected) && (query.enabled ?? true));
353
+ const queryKey = useMemo(() => getCapabilitiesQueryKey({
354
+ address: targetAddress,
355
+ chainId: parameters.chainId ?? chainId,
356
+ connector: activeConnector,
357
+ chainFilter: parameters.chainFilter,
358
+ }), [targetAddress, chainId, parameters.chainId, activeConnector, parameters.chainFilter]);
359
+ return useQuery({
360
+ ...query,
361
+ enabled,
362
+ gcTime: 0,
363
+ queryFn: activeConnector
364
+ ? async () => {
365
+ // When connected, use the standard flow
366
+ if (isConnected) {
367
+ return getCapabilities(config, {
368
+ ...rest,
369
+ address: targetAddress,
370
+ connector: activeConnector,
371
+ });
372
+ }
373
+ // When disconnected, make direct provider call
374
+ const provider = (await activeConnector.getProvider?.());
375
+ if (!provider)
376
+ throw new Error('Provider not available');
377
+ return provider.request({
378
+ method: 'wallet_getCapabilities',
379
+ params: [targetAddress, parameters.chainFilter],
380
+ });
381
+ }
382
+ : skipToken,
383
+ queryKey,
384
+ staleTime: 60_000, // Cache for 60 seconds since capabilities don't change often
385
+ });
386
+ }
387
+ /**
388
+ * Hook to sign messages using the unified wallet_sign method (ERC-7871).
389
+ * This combines the functionality of useSignMessage and useSignTypedData.
390
+ *
391
+ * @example
392
+ * ```tsx
393
+ * const { mutate: signMessage, data: signature, isPending } = useSign();
394
+ *
395
+ * // Personal sign (EIP-191)
396
+ * signMessage({
397
+ * request: {
398
+ * type: '0x45',
399
+ * data: { message: 'Hello World' },
400
+ * },
401
+ * });
402
+ *
403
+ * // Typed data sign (EIP-712)
404
+ * signMessage({
405
+ * request: {
406
+ * type: '0x01',
407
+ * data: {
408
+ * types: { ... },
409
+ * primaryType: 'Mail',
410
+ * domain: { ... },
411
+ * message: { ... },
412
+ * },
413
+ * },
414
+ * });
415
+ *
416
+ * // Sign on a specific chain (useful for smart accounts)
417
+ * signMessage({
418
+ * chainId: 8453, // Base
419
+ * request: {
420
+ * type: '0x45',
421
+ * data: { message: 'Hello from Base' },
422
+ * },
423
+ * });
424
+ * ```
425
+ */
426
+ export function useSign(parameters = {}) {
427
+ const { mutation } = parameters;
428
+ const config = useConfig(parameters);
429
+ return useMutation({
430
+ ...mutation,
431
+ mutationFn: async (variables) => {
432
+ return sign(config, variables);
433
+ },
434
+ mutationKey: ['sign'],
435
+ });
436
+ }
437
+ /**
438
+ * Hook to get the calls history for an account.
439
+ * Can be called without a connected account if address is provided.
440
+ *
441
+ * @example
442
+ * ```tsx
443
+ * // Get calls history for connected account
444
+ * const { data: history, isLoading } = useGetCallsHistory();
445
+ *
446
+ * // With specific address (works even when not connected)
447
+ * const { data } = useGetCallsHistory({ address: '0x...' });
448
+ *
449
+ * // With pagination
450
+ * const { data } = useGetCallsHistory({
451
+ * address: '0x...',
452
+ * limit: 10,
453
+ * sort: 'desc',
454
+ * });
455
+ * ```
456
+ */
457
+ export function useGetCallsHistory(parameters = {}) {
458
+ const { query = {}, ...rest } = parameters;
459
+ const config = useConfig(rest);
460
+ const chainId = useChainId({ config });
461
+ const { address: connectedAddress, connector, status } = useAccount({ config });
462
+ const connectors = useConnectors({ config });
463
+ // Use account connector if connected, otherwise find JAW connector from available connectors
464
+ const jawConnector = connectors.find((c) => c.id === 'jaw');
465
+ const activeConnector = parameters.connector ?? connector ?? jawConnector;
466
+ // Use explicit address if provided, otherwise fall back to connected address
467
+ const targetAddress = parameters.address ?? connectedAddress;
468
+ // Enable query if:
469
+ // 1. Connected (existing behavior), OR
470
+ // 2. Explicit address provided AND connector available (disconnected query)
471
+ const isConnected = status === 'connected' || (status === 'reconnecting' && activeConnector?.getProvider);
472
+ const canQueryDisconnected = Boolean(targetAddress && activeConnector?.getProvider);
473
+ const enabled = Boolean((isConnected || canQueryDisconnected) && (query.enabled ?? true));
474
+ const queryKey = useMemo(() => getCallsHistoryQueryKey({
475
+ address: targetAddress,
476
+ chainId: parameters.chainId ?? chainId,
477
+ connector: activeConnector,
478
+ index: parameters.index,
479
+ limit: parameters.limit,
480
+ sort: parameters.sort,
481
+ }), [targetAddress, chainId, parameters.chainId, activeConnector, parameters.index, parameters.limit, parameters.sort]);
482
+ return useQuery({
483
+ ...query,
484
+ enabled,
485
+ gcTime: 0,
486
+ queryFn: activeConnector
487
+ ? async () => {
488
+ // When connected, use the standard flow
489
+ if (isConnected) {
490
+ return getCallsHistory(config, {
491
+ ...rest,
492
+ address: targetAddress,
493
+ connector: activeConnector,
494
+ });
495
+ }
496
+ // When disconnected but have address, make direct provider call
497
+ const provider = (await activeConnector.getProvider?.());
498
+ if (!provider)
499
+ throw new Error('Provider not available');
500
+ if (!targetAddress)
501
+ throw new Error('Address is required when not connected');
502
+ return provider.request({
503
+ method: 'wallet_getCallsHistory',
504
+ params: [{
505
+ address: targetAddress,
506
+ chainId: parameters.chainId,
507
+ index: parameters.index,
508
+ limit: parameters.limit,
509
+ sort: parameters.sort,
510
+ }],
511
+ });
512
+ }
513
+ : skipToken,
514
+ queryKey,
515
+ staleTime: 30_000, // Cache for 30 seconds
516
+ });
517
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@jaw.id/wagmi",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ "./package.json": "./package.json",
10
+ ".": {
11
+ "@jaw-mono/source": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/JustaName-id/jaw-mono.git",
20
+ "directory": "packages/wagmi"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "dependencies": {
30
+ "@jaw.id/core": "0.0.1",
31
+ "@wagmi/core": "^3.0.0",
32
+ "tslib": "^2.3.0",
33
+ "viem": "^2.38.2"
34
+ },
35
+ "peerDependencies": {
36
+ "@tanstack/react-query": ">=5.0.0",
37
+ "react": ">=18.0.0",
38
+ "wagmi": ">=3.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/react": "^19.0.0"
42
+ }
43
+ }