@vaultkeepr/smart-account 0.1.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,635 @@
1
+ // src/kernel.ts
2
+ import {
3
+ createPublicClient,
4
+ http
5
+ } from "viem";
6
+ import { toKernelSmartAccount } from "permissionless/accounts";
7
+ import { createPimlicoClient } from "permissionless/clients/pimlico";
8
+ import {
9
+ createBundlerClient,
10
+ entryPoint07Address
11
+ } from "viem/account-abstraction";
12
+
13
+ // src/config.ts
14
+ import { base } from "viem/chains";
15
+ var TARGET_CHAIN = base;
16
+ function getPimlicoApiKey() {
17
+ let key = "";
18
+ try {
19
+ key = process.env.PIMLICO_API_KEY || "";
20
+ } catch {
21
+ }
22
+ if (key) return key;
23
+ try {
24
+ key = process.env.NEXT_PUBLIC_PIMLICO_API_KEY || "";
25
+ } catch {
26
+ }
27
+ return key;
28
+ }
29
+ function getPimlicoUrl() {
30
+ let proxyUrl = "";
31
+ try {
32
+ proxyUrl = process.env.PIMLICO_PROXY_URL || "";
33
+ } catch {
34
+ }
35
+ if (!proxyUrl) {
36
+ try {
37
+ proxyUrl = process.env.NEXT_PUBLIC_PIMLICO_PROXY_URL || "";
38
+ } catch {
39
+ }
40
+ }
41
+ if (!proxyUrl) {
42
+ try {
43
+ proxyUrl = process.env.EXPO_PUBLIC_PIMLICO_PROXY_URL || "";
44
+ } catch {
45
+ }
46
+ }
47
+ if (proxyUrl) {
48
+ return proxyUrl;
49
+ }
50
+ throw new Error(
51
+ "[SmartAccount] Aucun proxy bundler configure. Configurez PIMLICO_PROXY_URL (le fallback direct api.pimlico.io a ete supprime pour ne pas exposer la cle API dans le bundle client)."
52
+ );
53
+ }
54
+ function isSmartAccountConfigured() {
55
+ let proxyUrl = "";
56
+ try {
57
+ proxyUrl = process.env.PIMLICO_PROXY_URL || "";
58
+ } catch {
59
+ }
60
+ if (!proxyUrl) {
61
+ try {
62
+ proxyUrl = process.env.NEXT_PUBLIC_PIMLICO_PROXY_URL || "";
63
+ } catch {
64
+ }
65
+ }
66
+ if (!proxyUrl) {
67
+ try {
68
+ proxyUrl = process.env.EXPO_PUBLIC_PIMLICO_PROXY_URL || "";
69
+ } catch {
70
+ }
71
+ }
72
+ return !!proxyUrl;
73
+ }
74
+ function getTargetChain() {
75
+ return TARGET_CHAIN;
76
+ }
77
+
78
+ // src/kernel.ts
79
+ import { logger, redactAddress } from "@vaultkeepr/logger";
80
+ async function computeSmartAccountAddress(ownerAccount) {
81
+ const publicClient = createPublicClient({
82
+ chain: TARGET_CHAIN,
83
+ transport: http()
84
+ });
85
+ const kernelAccount = await toKernelSmartAccount({
86
+ client: publicClient,
87
+ owners: [ownerAccount],
88
+ entryPoint: {
89
+ address: entryPoint07Address,
90
+ version: "0.7"
91
+ }
92
+ });
93
+ return kernelAccount.address;
94
+ }
95
+ var _cached = null;
96
+ var _cachedKey = null;
97
+ async function createVaultSmartAccount(params) {
98
+ const { ownerAccount, mode } = params;
99
+ const cacheKey = `${mode}:${ownerAccount.address.toLowerCase()}`;
100
+ if (_cached && _cachedKey === cacheKey) return _cached;
101
+ const publicClient = createPublicClient({
102
+ chain: TARGET_CHAIN,
103
+ transport: http()
104
+ });
105
+ const kernelAccount = await toKernelSmartAccount({
106
+ client: publicClient,
107
+ owners: [ownerAccount],
108
+ entryPoint: {
109
+ address: entryPoint07Address,
110
+ version: "0.7"
111
+ }
112
+ });
113
+ const pimlicoUrl = getPimlicoUrl();
114
+ const pimlicoClient = createPimlicoClient({
115
+ transport: http(pimlicoUrl),
116
+ entryPoint: {
117
+ address: entryPoint07Address,
118
+ version: "0.7"
119
+ }
120
+ });
121
+ const bundlerClient = createBundlerClient({
122
+ account: kernelAccount,
123
+ chain: TARGET_CHAIN,
124
+ transport: http(pimlicoUrl),
125
+ paymaster: {
126
+ async getPaymasterData(parameters) {
127
+ return pimlicoClient.getPaymasterData(parameters);
128
+ },
129
+ async getPaymasterStubData(parameters) {
130
+ return pimlicoClient.getPaymasterStubData(parameters);
131
+ }
132
+ },
133
+ userOperation: {
134
+ async estimateFeesPerGas() {
135
+ return (await pimlicoClient.getUserOperationGasPrice()).fast;
136
+ }
137
+ }
138
+ });
139
+ const result = {
140
+ address: kernelAccount.address,
141
+ ownerAddress: ownerAccount.address,
142
+ ownerAccount,
143
+ client: bundlerClient,
144
+ chain: TARGET_CHAIN,
145
+ mode
146
+ };
147
+ _cached = result;
148
+ _cachedKey = cacheKey;
149
+ logger.debug(
150
+ `[SmartAccount] Kernel cr\xE9\xE9 (${mode}) \u2014 SA: ${redactAddress(kernelAccount.address)} | Owner: ${redactAddress(ownerAccount.address)}`
151
+ );
152
+ return result;
153
+ }
154
+ function getCachedSmartAccountAddress() {
155
+ return _cached?.address ?? null;
156
+ }
157
+ function getCachedSmartAccountMode() {
158
+ return _cached?.mode ?? null;
159
+ }
160
+ function clearSmartAccountCache() {
161
+ _cached = null;
162
+ _cachedKey = null;
163
+ }
164
+
165
+ // src/owner.ts
166
+ import { getHiddenWalletFromPassword, getHiddenWalletLegacy } from "@vaultkeepr/core";
167
+ async function getOwnerFromPassword(password, secretKey) {
168
+ return getHiddenWalletFromPassword(password, secretKey);
169
+ }
170
+
171
+ // src/cidRegistry.ts
172
+ import {
173
+ createPublicClient as createPublicClient2,
174
+ encodeFunctionData,
175
+ http as http2
176
+ } from "viem";
177
+ import { logger as logger2 } from "@vaultkeepr/logger";
178
+ var CID_REGISTRY_ABI = [
179
+ {
180
+ name: "setCid",
181
+ type: "function",
182
+ stateMutability: "nonpayable",
183
+ inputs: [{ name: "cid", type: "string" }],
184
+ outputs: []
185
+ },
186
+ {
187
+ name: "deleteCid",
188
+ type: "function",
189
+ stateMutability: "nonpayable",
190
+ inputs: [],
191
+ outputs: []
192
+ },
193
+ {
194
+ name: "getCid",
195
+ type: "function",
196
+ stateMutability: "view",
197
+ inputs: [{ name: "account", type: "address" }],
198
+ outputs: [{ name: "", type: "string" }]
199
+ },
200
+ {
201
+ name: "hasCid",
202
+ type: "function",
203
+ stateMutability: "view",
204
+ inputs: [{ name: "account", type: "address" }],
205
+ outputs: [{ name: "", type: "bool" }]
206
+ },
207
+ {
208
+ name: "getUpdatedAt",
209
+ type: "function",
210
+ stateMutability: "view",
211
+ inputs: [{ name: "account", type: "address" }],
212
+ outputs: [{ name: "", type: "uint256" }]
213
+ },
214
+ {
215
+ name: "nextAllowedUpdate",
216
+ type: "function",
217
+ stateMutability: "view",
218
+ inputs: [{ name: "account", type: "address" }],
219
+ outputs: [{ name: "", type: "uint256" }]
220
+ },
221
+ {
222
+ name: "cooldownSeconds",
223
+ type: "function",
224
+ stateMutability: "view",
225
+ inputs: [],
226
+ outputs: [{ name: "", type: "uint256" }]
227
+ }
228
+ ];
229
+ function getCidRegistryAddress() {
230
+ let addr = "";
231
+ try {
232
+ addr = process.env.CID_REGISTRY_ADDRESS || "";
233
+ } catch {
234
+ }
235
+ if (addr) return addr;
236
+ try {
237
+ addr = process.env.NEXT_PUBLIC_CID_REGISTRY_ADDRESS || "";
238
+ } catch {
239
+ }
240
+ if (addr) return addr;
241
+ try {
242
+ addr = process.env.EXPO_PUBLIC_CID_REGISTRY_ADDRESS || "";
243
+ } catch {
244
+ }
245
+ if (addr) return addr;
246
+ return "0x0000000000000000000000000000000000000000";
247
+ }
248
+ function isCidRegistryConfigured() {
249
+ return getCidRegistryAddress() !== "0x0000000000000000000000000000000000000000" && isSmartAccountConfigured();
250
+ }
251
+ async function publishCidOnChain(smartAccount, cid) {
252
+ if (!isCidRegistryConfigured()) {
253
+ logger2.warn(
254
+ "[CidRegistry] Non configur\xE9, skip on-chain publish"
255
+ );
256
+ return null;
257
+ }
258
+ try {
259
+ const calldata = encodeFunctionData({
260
+ abi: CID_REGISTRY_ABI,
261
+ functionName: "setCid",
262
+ args: [cid]
263
+ });
264
+ const userOpHash = await smartAccount.client.sendUserOperation({
265
+ calls: [
266
+ {
267
+ to: getCidRegistryAddress(),
268
+ data: calldata,
269
+ value: BigInt(0)
270
+ }
271
+ ]
272
+ });
273
+ logger2.debug(
274
+ `[CidRegistry] UserOp soumise: ${userOpHash}`
275
+ );
276
+ const receipt = await smartAccount.client.waitForUserOperationReceipt({
277
+ hash: userOpHash
278
+ });
279
+ logger2.debug(
280
+ `[CidRegistry] CID publi\xE9 on-chain \u2014 tx: ${receipt.receipt.transactionHash} | CID: ${cid.slice(0, 20)}...`
281
+ );
282
+ return receipt.receipt.transactionHash;
283
+ } catch (error) {
284
+ logger2.error("[CidRegistry] Erreur:", error instanceof Error ? error.message : error);
285
+ return null;
286
+ }
287
+ }
288
+ async function readCidOnChain(smartAccountAddress) {
289
+ if (!isCidRegistryConfigured()) return null;
290
+ try {
291
+ const publicClient = createPublicClient2({
292
+ chain: TARGET_CHAIN,
293
+ transport: http2()
294
+ });
295
+ const cid = await publicClient.readContract({
296
+ address: getCidRegistryAddress(),
297
+ abi: CID_REGISTRY_ABI,
298
+ functionName: "getCid",
299
+ args: [smartAccountAddress]
300
+ });
301
+ return cid && cid.length > 0 ? cid : null;
302
+ } catch (error) {
303
+ logger2.error("[CidRegistry] Erreur lecture:", error instanceof Error ? error.message : error);
304
+ return null;
305
+ }
306
+ }
307
+
308
+ // src/onChainSync.ts
309
+ import { logger as logger3 } from "@vaultkeepr/logger";
310
+ var DEFAULT_DEBOUNCE_MS = 5 * 60 * 1e3;
311
+ var _pendingCid = null;
312
+ var _pendingSmartAccount = null;
313
+ var _debounceTimer = null;
314
+ var _lastPublishedCid = null;
315
+ var _lastPublishTime = 0;
316
+ var _isPublishing = false;
317
+ function scheduleCidOnChainSync(smartAccount, cid, debounceMs = DEFAULT_DEBOUNCE_MS) {
318
+ if (!isCidRegistryConfigured()) return;
319
+ if (cid === _lastPublishedCid) {
320
+ logger3.debug("[OnChainSync] CID identique, skip");
321
+ return;
322
+ }
323
+ const sa = smartAccount ?? _pendingSmartAccount;
324
+ if (!sa) {
325
+ logger3.warn("[OnChainSync] Pas de Smart Account disponible, skip");
326
+ return;
327
+ }
328
+ _pendingCid = cid;
329
+ _pendingSmartAccount = sa;
330
+ if (_debounceTimer) {
331
+ clearTimeout(_debounceTimer);
332
+ }
333
+ _debounceTimer = setTimeout(() => {
334
+ _executeSync();
335
+ }, debounceMs);
336
+ logger3.debug(
337
+ `[OnChainSync] CID planifi\xE9 dans ${(debounceMs / 1e3).toFixed(0)}s : ${cid.slice(0, 20)}...`
338
+ );
339
+ }
340
+ async function flushCidOnChainSync() {
341
+ logger3.debug(
342
+ "[OnChainSync] flush demand\xE9 \u2014 pending:",
343
+ _pendingCid?.slice(0, 20),
344
+ "SA:",
345
+ !!_pendingSmartAccount
346
+ );
347
+ if (!_pendingCid || !_pendingSmartAccount) {
348
+ logger3.debug("[OnChainSync] Rien \xE0 flush");
349
+ return true;
350
+ }
351
+ if (_debounceTimer) {
352
+ clearTimeout(_debounceTimer);
353
+ _debounceTimer = null;
354
+ }
355
+ logger3.debug("[OnChainSync] Flush imm\xE9diat...");
356
+ return _executeSync();
357
+ }
358
+ function getOnChainSyncStatus() {
359
+ return {
360
+ hasPending: _pendingCid !== null && _pendingCid !== _lastPublishedCid,
361
+ pendingCid: _pendingCid,
362
+ lastPublishedCid: _lastPublishedCid,
363
+ lastPublishTime: _lastPublishTime,
364
+ isPublishing: _isPublishing
365
+ };
366
+ }
367
+ function resetOnChainSync() {
368
+ if (_debounceTimer) {
369
+ clearTimeout(_debounceTimer);
370
+ _debounceTimer = null;
371
+ }
372
+ _pendingCid = null;
373
+ _pendingSmartAccount = null;
374
+ _lastPublishedCid = null;
375
+ _lastPublishTime = 0;
376
+ _isPublishing = false;
377
+ }
378
+ async function _executeSync() {
379
+ if (!_pendingCid || !_pendingSmartAccount || _isPublishing) return false;
380
+ if (_pendingCid === _lastPublishedCid) {
381
+ _pendingCid = null;
382
+ return true;
383
+ }
384
+ _isPublishing = true;
385
+ const cid = _pendingCid;
386
+ const sa = _pendingSmartAccount;
387
+ try {
388
+ logger3.debug(`[OnChainSync] Publication on-chain: ${cid.slice(0, 20)}...`);
389
+ const txHash = await publishCidOnChain(sa, cid);
390
+ if (txHash) {
391
+ _lastPublishedCid = cid;
392
+ _lastPublishTime = Date.now();
393
+ _pendingCid = null;
394
+ logger3.debug(
395
+ `[OnChainSync] \u2705 CID publi\xE9 on-chain : ${cid.slice(0, 20)}...`
396
+ );
397
+ return true;
398
+ }
399
+ logger3.warn("[OnChainSync] Publication \xE9chou\xE9e (null txHash)");
400
+ return false;
401
+ } catch (error) {
402
+ logger3.error("[OnChainSync] Erreur publication:", error instanceof Error ? error.message : error);
403
+ return false;
404
+ } finally {
405
+ _isPublishing = false;
406
+ }
407
+ }
408
+
409
+ // src/identity.ts
410
+ import { logger as logger4, redactAddress as redactAddress2 } from "@vaultkeepr/logger";
411
+ var _state = {
412
+ owner: null,
413
+ smartAccount: null,
414
+ mode: null,
415
+ initialized: false
416
+ };
417
+ var _listeners = /* @__PURE__ */ new Set();
418
+ function _notify() {
419
+ for (const fn of _listeners) {
420
+ try {
421
+ fn(_state);
422
+ } catch {
423
+ }
424
+ }
425
+ }
426
+ async function initIdentityFromPassword(password, secretKey) {
427
+ const owner = await getOwnerFromPassword(password, secretKey);
428
+ let sa = null;
429
+ if (isSmartAccountConfigured()) {
430
+ try {
431
+ sa = await createVaultSmartAccount({
432
+ ownerAccount: owner,
433
+ mode: "password"
434
+ });
435
+ } catch (e) {
436
+ logger4.warn("[Identity] SA init failed (non-blocking):", e.message);
437
+ }
438
+ }
439
+ _state = {
440
+ owner,
441
+ smartAccount: sa,
442
+ mode: "password",
443
+ initialized: true
444
+ };
445
+ _notify();
446
+ logger4.debug(
447
+ `[Identity] Connect\xE9 (password) \u2014 adresse: ${redactAddress2(owner.address)}` + (sa ? ` | SA: ${redactAddress2(sa.address)}` : "") + (secretKey ? ` | SK: \u2713` : " | no secretKey")
448
+ );
449
+ return {
450
+ address: owner.address,
451
+ smartAccountAddress: sa?.address ?? null
452
+ };
453
+ }
454
+ async function initIdentityFromSigner(signer, mode = "passkey") {
455
+ let sa = null;
456
+ if (isSmartAccountConfigured()) {
457
+ try {
458
+ sa = await createVaultSmartAccount({ ownerAccount: signer, mode });
459
+ } catch (e) {
460
+ logger4.warn("[Identity] SA init failed (non-blocking):", e.message);
461
+ }
462
+ }
463
+ _state = {
464
+ owner: signer,
465
+ smartAccount: sa,
466
+ mode,
467
+ initialized: true
468
+ };
469
+ _notify();
470
+ logger4.info(
471
+ `[Identity] Connected (${mode}) \u2014 address: ${signer.address}` + (sa ? ` | SA: ${sa.address}` : "")
472
+ );
473
+ return {
474
+ address: signer.address,
475
+ smartAccountAddress: sa?.address ?? null
476
+ };
477
+ }
478
+ function clearIdentity() {
479
+ _state = {
480
+ owner: null,
481
+ smartAccount: null,
482
+ mode: null,
483
+ initialized: false
484
+ };
485
+ clearSmartAccountCache();
486
+ _notify();
487
+ logger4.info("[Identity] Disconnected");
488
+ }
489
+ function getIdentityAddress() {
490
+ return _state.owner?.address ?? null;
491
+ }
492
+ function isIdentityConnected() {
493
+ return _state.initialized && _state.owner !== null;
494
+ }
495
+ function getIdentityOwner() {
496
+ return _state.owner;
497
+ }
498
+ function getIdentitySmartAccount() {
499
+ return _state.smartAccount;
500
+ }
501
+ function getIdentityMode() {
502
+ return _state.mode;
503
+ }
504
+ async function signMessageWithIdentity(message) {
505
+ if (!_state.owner) {
506
+ throw new Error("[Identity] Pas connect\xE9 \u2014 impossible de signer");
507
+ }
508
+ return _state.owner.signMessage({ message });
509
+ }
510
+ function subscribeIdentity(listener) {
511
+ _listeners.add(listener);
512
+ return () => {
513
+ _listeners.delete(listener);
514
+ };
515
+ }
516
+ function getIdentitySnapshot() {
517
+ return _state;
518
+ }
519
+
520
+ // src/useIdentity.ts
521
+ import { useSyncExternalStore, useCallback } from "react";
522
+ var _emptyState = {
523
+ owner: null,
524
+ smartAccount: null,
525
+ mode: null,
526
+ initialized: false
527
+ };
528
+ function getServerSnapshot() {
529
+ return _emptyState;
530
+ }
531
+ function useIdentity() {
532
+ const state = useSyncExternalStore(
533
+ subscribeIdentity,
534
+ getIdentitySnapshot,
535
+ getServerSnapshot
536
+ );
537
+ const signMessage = useCallback(
538
+ async (message) => {
539
+ return signMessageWithIdentity(message);
540
+ },
541
+ []
542
+ );
543
+ const disconnect = useCallback(() => {
544
+ clearIdentity();
545
+ }, []);
546
+ const notifyOnChainSync = useCallback(
547
+ (cid) => {
548
+ const sa = getIdentitySmartAccount();
549
+ if (!sa) return;
550
+ scheduleCidOnChainSync(sa, cid);
551
+ },
552
+ []
553
+ );
554
+ const cleanupOnLock = useCallback(async () => {
555
+ await flushCidOnChainSync().catch(() => {
556
+ });
557
+ clearSmartAccountCache();
558
+ resetOnChainSync();
559
+ clearIdentity();
560
+ }, []);
561
+ const flushOnChainSync = useCallback(async () => {
562
+ await flushCidOnChainSync().catch(() => {
563
+ });
564
+ }, []);
565
+ return {
566
+ address: state.owner?.address ?? null,
567
+ isConnected: state.initialized && state.owner !== null,
568
+ mode: state.mode,
569
+ smartAccountAddress: state.smartAccount?.address ?? null,
570
+ signMessage,
571
+ disconnect,
572
+ notifyOnChainSync,
573
+ cleanupOnLock,
574
+ flushOnChainSync
575
+ };
576
+ }
577
+
578
+ // src/secretKey.ts
579
+ import { bytesToHex } from "viem";
580
+ function generateSecretKey() {
581
+ const bytes = new Uint8Array(32);
582
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
583
+ globalThis.crypto.getRandomValues(bytes);
584
+ } else {
585
+ for (let i = 0; i < 32; i++) {
586
+ bytes[i] = Math.floor(Math.random() * 256);
587
+ }
588
+ }
589
+ return bytesToHex(bytes).slice(2);
590
+ }
591
+ function isValidSecretKey(key) {
592
+ return typeof key === "string" && /^[0-9a-f]{64}$/i.test(key);
593
+ }
594
+ function formatSecretKeyForDisplay(key) {
595
+ const clean = key.replace(/[^0-9a-f]/gi, "").toLowerCase();
596
+ return clean.match(/.{1,8}/g)?.join("-") ?? clean;
597
+ }
598
+ function parseSecretKeyInput(input) {
599
+ return input.replace(/[-\s]/g, "").toLowerCase();
600
+ }
601
+ export {
602
+ CID_REGISTRY_ABI,
603
+ clearIdentity,
604
+ clearSmartAccountCache,
605
+ computeSmartAccountAddress,
606
+ createVaultSmartAccount,
607
+ flushCidOnChainSync,
608
+ formatSecretKeyForDisplay,
609
+ generateSecretKey,
610
+ getCachedSmartAccountAddress,
611
+ getCachedSmartAccountMode,
612
+ getIdentityAddress,
613
+ getIdentityMode,
614
+ getIdentityOwner,
615
+ getIdentitySmartAccount,
616
+ getIdentitySnapshot,
617
+ getOnChainSyncStatus,
618
+ getOwnerFromPassword,
619
+ getPimlicoApiKey,
620
+ getTargetChain,
621
+ initIdentityFromPassword,
622
+ initIdentityFromSigner,
623
+ isCidRegistryConfigured,
624
+ isIdentityConnected,
625
+ isSmartAccountConfigured,
626
+ isValidSecretKey,
627
+ parseSecretKeyInput,
628
+ publishCidOnChain,
629
+ readCidOnChain,
630
+ resetOnChainSync,
631
+ scheduleCidOnChainSync,
632
+ signMessageWithIdentity,
633
+ subscribeIdentity,
634
+ useIdentity
635
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@vaultkeepr/smart-account",
3
+ "version": "0.1.1",
4
+ "description": "ERC-4337 Smart Account (Kernel) with Pimlico Paymaster — universal module for Web, Extension, iOS, and Android",
5
+ "license": "MIT",
6
+ "main": "./src/index.ts",
7
+ "module": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "import": "./src/index.ts",
13
+ "require": "./src/index.ts"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@vaultkeepr/logger": "0.1.1",
18
+ "@vaultkeepr/core": "0.1.1"
19
+ },
20
+ "peerDependencies": {
21
+ "permissionless": ">=0.3.0",
22
+ "viem": ">=2.0.0",
23
+ "react": "^18.0.0 || ^19.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.3.5",
27
+ "typescript": "^5.6.3",
28
+ "vitest": "^4.1.11",
29
+ "react": "^19.2.0",
30
+ "@types/react": "^19.0.0"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format cjs,esm --dts",
37
+ "clean": "rm -rf dist",
38
+ "test": "vitest run --passWithNoTests",
39
+ "lint": "eslint src/"
40
+ }
41
+ }