@imtbl/wallet 2.12.6 → 2.12.7-alpha.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 ADDED
@@ -0,0 +1,688 @@
1
+ # @imtbl/wallet
2
+
3
+ The Immutable Wallet SDK provides an EIP-1193 compliant Ethereum provider for interacting with Immutable's zkEVM and other supported blockchains. It handles authentication, transaction signing, wallet management, and external wallet linking out of the box.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Configuration Options](#configuration-options)
10
+ - [Using External Auth Packages](#using-external-auth-packages)
11
+ - [API Reference](#api-reference)
12
+ - [Chain Configuration](#chain-configuration)
13
+ - [Wallet Linking](#wallet-linking)
14
+ - [EIP-6963 Provider Discovery](#eip-6963-provider-discovery)
15
+ - [Events](#events)
16
+ - [Error Handling](#error-handling)
17
+ - [TypeScript](#typescript)
18
+ - [Related Packages](#related-packages)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @imtbl/wallet
24
+ # or
25
+ yarn add @imtbl/wallet
26
+ # or
27
+ pnpm add @imtbl/wallet
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ Connect to Immutable zkEVM with just a few lines of code:
33
+
34
+ ```typescript
35
+ import { connectWallet } from '@imtbl/wallet';
36
+
37
+ // Create a wallet provider
38
+ const provider = await connectWallet();
39
+
40
+ // Request accounts (triggers login if user is not authenticated)
41
+ const accounts = await provider.request({ method: 'eth_requestAccounts' });
42
+ console.log('Connected:', accounts[0]);
43
+
44
+ // Send a transaction
45
+ const txHash = await provider.request({
46
+ method: 'eth_sendTransaction',
47
+ params: [{
48
+ to: '0x...',
49
+ value: '0x...',
50
+ }],
51
+ });
52
+ ```
53
+
54
+ That's it! The wallet handles authentication, user registration, and transaction signing automatically.
55
+
56
+ ## Configuration Options
57
+
58
+ ### Custom Client ID
59
+
60
+ ```typescript
61
+ const provider = await connectWallet({
62
+ clientId: 'your-client-id',
63
+ });
64
+ ```
65
+
66
+ ### Chain Selection
67
+
68
+ ```typescript
69
+ import {
70
+ connectWallet,
71
+ IMMUTABLE_ZKEVM_MAINNET_CHAIN,
72
+ IMMUTABLE_ZKEVM_TESTNET_CHAIN,
73
+ } from '@imtbl/wallet';
74
+
75
+ // Connect to mainnet
76
+ const provider = await connectWallet({
77
+ chains: [IMMUTABLE_ZKEVM_MAINNET_CHAIN],
78
+ });
79
+
80
+ // Connect to testnet (default)
81
+ const provider = await connectWallet({
82
+ chains: [IMMUTABLE_ZKEVM_TESTNET_CHAIN],
83
+ });
84
+ ```
85
+
86
+ ### Popup Overlay Options
87
+
88
+ ```typescript
89
+ const provider = await connectWallet({
90
+ popupOverlayOptions: {
91
+ disableGenericPopupOverlay: true,
92
+ disableBlockedPopupOverlay: true,
93
+ },
94
+ });
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Using External Auth Packages
100
+
101
+ By default, `@imtbl/wallet` handles authentication internally. However, if your application already uses Immutable's auth packages for session management, you can integrate them with the wallet.
102
+
103
+ | Package | Use Case |
104
+ |---------|----------|
105
+ | `@imtbl/auth-next-client` | Next.js apps with server-side session management |
106
+ | `@imtbl/auth` | Non-Next.js apps needing custom auth control |
107
+
108
+ ### With @imtbl/auth-next-client (Next.js)
109
+
110
+ If you're using NextAuth for server-side session management, pass the `getUser` function from `useImmutableSession`:
111
+
112
+ **Install dependencies:**
113
+
114
+ ```bash
115
+ npm install @imtbl/wallet @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
116
+ ```
117
+
118
+ **Integrate with the wallet:**
119
+
120
+ ```tsx
121
+ 'use client';
122
+
123
+ import { connectWallet } from '@imtbl/wallet';
124
+ import { useImmutableSession } from '@imtbl/auth-next-client';
125
+ import { useState } from 'react';
126
+
127
+ export function WalletConnect() {
128
+ const { getUser } = useImmutableSession();
129
+ const [provider, setProvider] = useState(null);
130
+
131
+ const handleConnect = async () => {
132
+ // Pass getUser to share session with the wallet
133
+ const walletProvider = await connectWallet({ getUser });
134
+
135
+ const accounts = await walletProvider.request({
136
+ method: 'eth_requestAccounts'
137
+ });
138
+
139
+ console.log('Connected:', accounts[0]);
140
+ setProvider(walletProvider);
141
+ };
142
+
143
+ return <button onClick={handleConnect}>Connect Wallet</button>;
144
+ }
145
+ ```
146
+
147
+ **Benefits:**
148
+ - Server-side session management with secure token storage
149
+ - Automatic token refresh via NextAuth callbacks
150
+ - SSR compatibility
151
+ - Single source of truth for authentication state
152
+
153
+ ### With @imtbl/auth (Direct)
154
+
155
+ For applications needing full control over authentication without NextAuth:
156
+
157
+ **Install dependencies:**
158
+
159
+ ```bash
160
+ npm install @imtbl/wallet @imtbl/auth
161
+ ```
162
+
163
+ **Integrate with the wallet:**
164
+
165
+ ```typescript
166
+ import { connectWallet } from '@imtbl/wallet';
167
+ import { Auth } from '@imtbl/auth';
168
+
169
+ // Create your Auth instance
170
+ const auth = new Auth({
171
+ clientId: 'your-client-id',
172
+ redirectUri: 'https://your-app.com/callback',
173
+ // ... other options
174
+ });
175
+
176
+ // Create a getUser function that wraps your Auth instance
177
+ const getUser = async (forceRefresh?: boolean) => {
178
+ if (forceRefresh) {
179
+ return auth.forceUserRefresh();
180
+ }
181
+ return auth.getUser();
182
+ };
183
+
184
+ // Pass to connectWallet
185
+ const provider = await connectWallet({ getUser });
186
+ ```
187
+
188
+ **When to use this approach:**
189
+ - You need custom login/logout flows
190
+ - You want to manage auth state outside the wallet
191
+ - You're integrating with an existing auth system
192
+
193
+ ---
194
+
195
+ ## API Reference
196
+
197
+ ### `connectWallet(options?)`
198
+
199
+ Creates an EIP-1193 compliant provider for Immutable zkEVM or other supported chains.
200
+
201
+ #### Options
202
+
203
+ | Option | Type | Default | Description |
204
+ |--------|------|---------|-------------|
205
+ | `clientId` | `string` | Auto-detected | Immutable client ID |
206
+ | `chains` | `ChainConfig[]` | `[testnet, mainnet]` | Chain configurations |
207
+ | `initialChainId` | `number` | First chain | Initial chain to connect to |
208
+ | `popupOverlayOptions` | `PopupOverlayOptions` | - | Options for login popup overlays |
209
+ | `announceProvider` | `boolean` | `true` | Whether to announce via EIP-6963 |
210
+ | `feeTokenSymbol` | `string` | `'IMX'` | Preferred token symbol for relayer fees |
211
+ | `jsonRpcReferrer` | `string` | - | Referrer URL sent with JSON-RPC requests |
212
+ | `crossSdkBridgeEnabled` | `boolean` | `false` | Enable cross-SDK bridge mode |
213
+ | `forceScwDeployBeforeMessageSignature` | `boolean` | `false` | Force SCW deployment before message signature |
214
+ | `getUser` | `GetUserFunction` | Internal | Custom function to get user (for external auth integration) |
215
+
216
+ #### Returns
217
+
218
+ `Promise<Provider>` - An EIP-1193 compliant provider.
219
+
220
+ ### Provider Methods
221
+
222
+ The returned provider supports standard Ethereum JSON-RPC methods:
223
+
224
+ ```typescript
225
+ // Get accounts (triggers login if needed)
226
+ const accounts = await provider.request({ method: 'eth_requestAccounts' });
227
+
228
+ // Get current accounts (returns empty if not connected)
229
+ const accounts = await provider.request({ method: 'eth_accounts' });
230
+
231
+ // Get chain ID
232
+ const chainId = await provider.request({ method: 'eth_chainId' });
233
+
234
+ // Send transaction
235
+ const txHash = await provider.request({
236
+ method: 'eth_sendTransaction',
237
+ params: [{
238
+ to: '0x...',
239
+ value: '0x...',
240
+ data: '0x...',
241
+ }],
242
+ });
243
+
244
+ // Sign message (personal_sign)
245
+ const signature = await provider.request({
246
+ method: 'personal_sign',
247
+ params: ['0x...message', '0x...address'],
248
+ });
249
+
250
+ // Sign typed data (EIP-712)
251
+ const signature = await provider.request({
252
+ method: 'eth_signTypedData_v4',
253
+ params: ['0x...address', JSON.stringify(typedData)],
254
+ });
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Chain Configuration
260
+
261
+ ### Chain ID Constants
262
+
263
+ ```typescript
264
+ import {
265
+ IMMUTABLE_ZKEVM_MAINNET_CHAIN_ID, // 13371
266
+ IMMUTABLE_ZKEVM_TESTNET_CHAIN_ID, // 13473
267
+ ARBITRUM_ONE_CHAIN_ID, // 42161
268
+ ARBITRUM_SEPOLIA_CHAIN_ID, // 421614
269
+ } from '@imtbl/wallet';
270
+ ```
271
+
272
+ ### Preset Chain Configurations
273
+
274
+ #### Immutable zkEVM
275
+
276
+ ```typescript
277
+ import {
278
+ IMMUTABLE_ZKEVM_MAINNET_CHAIN, // Mainnet config
279
+ IMMUTABLE_ZKEVM_TESTNET_CHAIN, // Testnet config
280
+ DEFAULT_CHAINS, // [testnet, mainnet]
281
+ } from '@imtbl/wallet';
282
+
283
+ // Use testnet only
284
+ const provider = await connectWallet({
285
+ chains: [IMMUTABLE_ZKEVM_TESTNET_CHAIN],
286
+ });
287
+
288
+ // Use mainnet only
289
+ const provider = await connectWallet({
290
+ chains: [IMMUTABLE_ZKEVM_MAINNET_CHAIN],
291
+ });
292
+
293
+ // Use both (default)
294
+ const provider = await connectWallet({
295
+ chains: DEFAULT_CHAINS,
296
+ });
297
+ ```
298
+
299
+ #### Arbitrum (Multi-chain Support)
300
+
301
+ ```typescript
302
+ import {
303
+ ARBITRUM_ONE_CHAIN, // Arbitrum One Mainnet
304
+ ARBITRUM_SEPOLIA_CHAIN, // Arbitrum Sepolia Testnet
305
+ } from '@imtbl/wallet';
306
+
307
+ // Connect to Arbitrum One
308
+ const provider = await connectWallet({
309
+ chains: [ARBITRUM_ONE_CHAIN],
310
+ });
311
+ ```
312
+
313
+ ### Chain Presets (Spread-friendly)
314
+
315
+ ```typescript
316
+ import {
317
+ IMMUTABLE_ZKEVM_MAINNET, // { chains: [mainnet] }
318
+ IMMUTABLE_ZKEVM_TESTNET, // { chains: [testnet] }
319
+ IMMUTABLE_ZKEVM_MULTICHAIN, // { chains: [testnet, mainnet] }
320
+ ARBITRUM_ONE, // { chains: [arbitrum one] }
321
+ ARBITRUM_SEPOLIA, // { chains: [arbitrum sepolia] }
322
+ } from '@imtbl/wallet';
323
+
324
+ // Easy to spread into connectWallet
325
+ const provider = await connectWallet({
326
+ ...IMMUTABLE_ZKEVM_MAINNET,
327
+ });
328
+ ```
329
+
330
+ ### Custom Chain Configuration
331
+
332
+ ```typescript
333
+ import type { ChainConfig } from '@imtbl/wallet';
334
+
335
+ const customChain: ChainConfig = {
336
+ chainId: 13473,
337
+ name: 'Immutable zkEVM Testnet',
338
+ rpcUrl: 'https://rpc.testnet.immutable.com',
339
+ relayerUrl: 'https://api.sandbox.immutable.com/relayer-mr',
340
+ apiUrl: 'https://api.sandbox.immutable.com',
341
+ passportDomain: 'https://passport.sandbox.immutable.com',
342
+ // Optional: Magic TEE config for dev environments
343
+ magicPublishableApiKey: 'pk_...',
344
+ magicProviderId: '...',
345
+ magicTeeBasePath: 'https://tee.express.magiclabs.com',
346
+ // Optional: Fee token (defaults to 'IMX')
347
+ feeTokenSymbol: 'IMX',
348
+ };
349
+ ```
350
+
351
+ ### Chain Registry Utilities
352
+
353
+ ```typescript
354
+ import { getChainConfig, getEvmChainFromChainId } from '@imtbl/wallet';
355
+
356
+ // Get chain config by chain ID
357
+ const config = getChainConfig(13371); // Returns IMMUTABLE_ZKEVM_MAINNET_CHAIN
358
+
359
+ // Get EVM chain type from chain ID
360
+ const evmChain = getEvmChainFromChainId(13371); // Returns EvmChain.ZKEVM
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Wallet Linking
366
+
367
+ Link external wallets (e.g., MetaMask, hardware wallets) to a user's Immutable Passport account.
368
+
369
+ ### Get Linked Addresses
370
+
371
+ Retrieve all wallet addresses linked to the current user's account:
372
+
373
+ ```typescript
374
+ import { getLinkedAddresses } from '@imtbl/wallet';
375
+ import { Auth } from '@imtbl/auth';
376
+ import { MultiRollupApiClients, createConfig } from '@imtbl/generated-clients';
377
+
378
+ const auth = new Auth({ /* ... */ });
379
+ const apiConfig = createConfig({ basePath: 'https://api.immutable.com' });
380
+ const apiClient = new MultiRollupApiClients({
381
+ indexer: apiConfig,
382
+ orderBook: apiConfig,
383
+ passport: apiConfig,
384
+ });
385
+
386
+ const linkedAddresses = await getLinkedAddresses(auth, apiClient);
387
+ console.log('Linked wallets:', linkedAddresses);
388
+ // ['0x1234...', '0x5678...']
389
+ ```
390
+
391
+ ### Link External Wallet
392
+
393
+ Link a new external wallet to the user's account:
394
+
395
+ ```typescript
396
+ import { linkExternalWallet } from '@imtbl/wallet';
397
+ import type { LinkWalletParams, LinkedWallet } from '@imtbl/wallet';
398
+
399
+ // 1. Get a signature from the external wallet
400
+ const walletAddress = '0x...'; // Address from MetaMask/external wallet
401
+ const nonce = 'unique-nonce-123'; // Generate a unique nonce
402
+ const message = `Link wallet ${walletAddress} with nonce ${nonce}`;
403
+
404
+ // Sign the message with the external wallet (e.g., using ethers.js or viem)
405
+ const signature = await externalWallet.signMessage(message);
406
+
407
+ // 2. Link the wallet
408
+ const params: LinkWalletParams = {
409
+ type: 'metamask', // Wallet type identifier
410
+ walletAddress,
411
+ signature,
412
+ nonce,
413
+ };
414
+
415
+ const linkedWallet: LinkedWallet = await linkExternalWallet(
416
+ auth,
417
+ apiClient,
418
+ params
419
+ );
420
+
421
+ console.log('Linked wallet:', linkedWallet);
422
+ // {
423
+ // address: '0x...',
424
+ // type: 'metamask',
425
+ // created_at: '2024-01-01T00:00:00Z',
426
+ // updated_at: '2024-01-01T00:00:00Z',
427
+ // clientName: 'your-client'
428
+ // }
429
+ ```
430
+
431
+ ### Linking Errors
432
+
433
+ The wallet linking API may return specific error codes:
434
+
435
+ | Error Code | Description |
436
+ |------------|-------------|
437
+ | `ALREADY_LINKED` | This wallet is already linked to an account |
438
+ | `MAX_WALLETS_LINKED` | Maximum number of linked wallets reached |
439
+ | `DUPLICATE_NONCE` | The nonce has already been used |
440
+ | `VALIDATION_ERROR` | Invalid signature or parameters |
441
+
442
+ ---
443
+
444
+ ## EIP-6963 Provider Discovery
445
+
446
+ The wallet automatically announces itself via [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) for wallet discovery by dApps.
447
+
448
+ ### Automatic Announcement
449
+
450
+ By default, `connectWallet` announces the provider:
451
+
452
+ ```typescript
453
+ const provider = await connectWallet();
454
+ // Provider is automatically announced via EIP-6963
455
+ ```
456
+
457
+ ### Disable Announcement
458
+
459
+ ```typescript
460
+ const provider = await connectWallet({
461
+ announceProvider: false, // Don't announce via EIP-6963
462
+ });
463
+ ```
464
+
465
+ ### Manual Announcement
466
+
467
+ ```typescript
468
+ import { announceProvider, passportProviderInfo } from '@imtbl/wallet';
469
+
470
+ // Manually announce a provider
471
+ announceProvider({
472
+ info: passportProviderInfo,
473
+ provider: yourProvider,
474
+ });
475
+ ```
476
+
477
+ ### Provider Info
478
+
479
+ ```typescript
480
+ import { passportProviderInfo } from '@imtbl/wallet';
481
+
482
+ console.log(passportProviderInfo);
483
+ // {
484
+ // uuid: '...',
485
+ // name: 'Immutable Passport',
486
+ // icon: '...',
487
+ // rdns: 'com.immutable.passport'
488
+ // }
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Events
494
+
495
+ The provider emits standard EIP-1193 events:
496
+
497
+ ```typescript
498
+ // Account changes
499
+ provider.on('accountsChanged', (accounts: string[]) => {
500
+ console.log('Accounts changed:', accounts);
501
+ });
502
+
503
+ // Chain changes
504
+ provider.on('chainChanged', (chainId: string) => {
505
+ console.log('Chain changed:', chainId);
506
+ });
507
+
508
+ // Disconnection
509
+ provider.on('disconnect', (error: Error) => {
510
+ console.log('Disconnected:', error);
511
+ });
512
+
513
+ // Remove listener
514
+ provider.removeListener('accountsChanged', handler);
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Error Handling
520
+
521
+ ### WalletError
522
+
523
+ The SDK throws `WalletError` for wallet-related errors:
524
+
525
+ ```typescript
526
+ import { connectWallet, WalletError, WalletErrorType } from '@imtbl/wallet';
527
+
528
+ try {
529
+ const provider = await connectWallet();
530
+ const accounts = await provider.request({ method: 'eth_requestAccounts' });
531
+ } catch (error) {
532
+ if (error instanceof WalletError) {
533
+ switch (error.type) {
534
+ case WalletErrorType.NOT_LOGGED_IN_ERROR:
535
+ console.log('User is not logged in');
536
+ break;
537
+ case WalletErrorType.WALLET_CONNECTION_ERROR:
538
+ console.log('Failed to connect wallet:', error.message);
539
+ break;
540
+ case WalletErrorType.TRANSACTION_REJECTED:
541
+ console.log('User rejected the transaction');
542
+ break;
543
+ case WalletErrorType.UNAUTHORIZED:
544
+ console.log('Unauthorized - call eth_requestAccounts first');
545
+ break;
546
+ case WalletErrorType.GUARDIAN_ERROR:
547
+ console.log('Guardian validation failed');
548
+ break;
549
+ case WalletErrorType.INVALID_CONFIGURATION:
550
+ console.log('Invalid wallet configuration');
551
+ break;
552
+ case WalletErrorType.SERVICE_UNAVAILABLE_ERROR:
553
+ console.log('Service temporarily unavailable');
554
+ break;
555
+ default:
556
+ console.error('Wallet error:', error.message);
557
+ }
558
+ } else {
559
+ console.error('Unexpected error:', error);
560
+ }
561
+ }
562
+ ```
563
+
564
+ ### Error Types
565
+
566
+ | Error Type | Description |
567
+ |------------|-------------|
568
+ | `NOT_LOGGED_IN_ERROR` | User is not authenticated |
569
+ | `WALLET_CONNECTION_ERROR` | Failed to connect or link wallet |
570
+ | `TRANSACTION_REJECTED` | User rejected a transaction |
571
+ | `UNAUTHORIZED` | Operation requires authentication |
572
+ | `GUARDIAN_ERROR` | Guardian (security) validation failed |
573
+ | `INVALID_CONFIGURATION` | Invalid wallet configuration |
574
+ | `SERVICE_UNAVAILABLE_ERROR` | Backend service unavailable |
575
+
576
+ ### JSON-RPC Errors
577
+
578
+ For low-level RPC errors:
579
+
580
+ ```typescript
581
+ import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from '@imtbl/wallet';
582
+
583
+ // Standard provider error codes (EIP-1193)
584
+ ProviderErrorCode.USER_REJECTED_REQUEST // 4001
585
+ ProviderErrorCode.UNAUTHORIZED // 4100
586
+ ProviderErrorCode.UNSUPPORTED_METHOD // 4200
587
+ ProviderErrorCode.DISCONNECTED // 4900
588
+
589
+ // Standard RPC error codes
590
+ RpcErrorCode.INVALID_REQUEST // -32600
591
+ RpcErrorCode.METHOD_NOT_FOUND // -32601
592
+ RpcErrorCode.INVALID_PARAMS // -32602
593
+ RpcErrorCode.INTERNAL_ERROR // -32603
594
+ ```
595
+
596
+ ---
597
+
598
+ ## TypeScript
599
+
600
+ Full TypeScript support is included:
601
+
602
+ ```typescript
603
+ import type {
604
+ // Connection options
605
+ ConnectWalletOptions,
606
+ ChainConfig,
607
+ PopupOverlayOptions,
608
+
609
+ // Provider types
610
+ Provider,
611
+ RequestArguments,
612
+
613
+ // User types
614
+ GetUserFunction,
615
+ User,
616
+ UserProfile,
617
+ UserZkEvm,
618
+
619
+ // Wallet linking
620
+ LinkWalletParams,
621
+ LinkedWallet,
622
+
623
+ // Transaction types
624
+ TypedDataPayload,
625
+ MetaTransaction,
626
+ RelayerTransaction,
627
+ RelayerTransactionStatus,
628
+ FeeOption,
629
+
630
+ // Event types
631
+ WalletEventMap,
632
+ ProviderEventMap,
633
+ AccountsChangedEvent,
634
+
635
+ // EIP-6963
636
+ EIP6963ProviderDetail,
637
+ EIP6963ProviderInfo,
638
+ } from '@imtbl/wallet';
639
+ ```
640
+
641
+ ---
642
+
643
+ ## Advanced Usage
644
+
645
+ ### Direct Provider Access
646
+
647
+ For advanced use cases, you can access the provider classes directly:
648
+
649
+ ```typescript
650
+ import { ZkEvmProvider, SequenceProvider } from '@imtbl/wallet';
651
+ ```
652
+
653
+ ### Relayer Client
654
+
655
+ For direct relayer interaction:
656
+
657
+ ```typescript
658
+ import { RelayerClient } from '@imtbl/wallet';
659
+ ```
660
+
661
+ ### Wallet Helpers
662
+
663
+ Utility functions for wallet operations:
664
+
665
+ ```typescript
666
+ import * as walletHelpers from '@imtbl/wallet';
667
+ ```
668
+
669
+ ---
670
+
671
+ ## Related Packages
672
+
673
+ ### Optional Auth Integrations
674
+
675
+ These packages are only needed if you want to manage authentication separately from the wallet:
676
+
677
+ | Package | Description |
678
+ |---------|-------------|
679
+ | [`@imtbl/auth-next-client`](../auth-next-client/README.md) | NextAuth client-side hooks for Next.js apps |
680
+ | [`@imtbl/auth-next-server`](../auth-next-server/README.md) | NextAuth server-side configuration |
681
+ | [`@imtbl/auth`](../auth/README.md) | Core authentication for custom auth flows |
682
+
683
+ ### Other Immutable Packages
684
+
685
+ | Package | Description |
686
+ |---------|-------------|
687
+ | [`@imtbl/generated-clients`](../generated-clients/README.md) | API clients for Immutable services |
688
+ | [`@imtbl/metrics`](../metrics/README.md) | Metrics and analytics utilities |