@imtbl/wallet
v2.19.0
Published
Wallet SDK for Immutable
Keywords
Readme
@imtbl/wallet
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.
Table of Contents
- Installation
- Quick Start
- Configuration Options
- Using External Auth Packages
- API Reference
- Chain Configuration
- Wallet Linking
- EIP-6963 Provider Discovery
- Events
- Error Handling
- TypeScript
- Related Packages
Installation
npm install @imtbl/wallet
# or
yarn add @imtbl/wallet
# or
pnpm add @imtbl/walletQuick Start
Connect to Immutable zkEVM with just a few lines of code:
import { connectWallet } from '@imtbl/wallet';
// Create a wallet provider
const provider = await connectWallet();
// Request accounts (triggers login if user is not authenticated)
const accounts = await provider.request({ method: 'eth_requestAccounts' });
console.log('Connected:', accounts[0]);
// Send a transaction
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x...',
value: '0x...',
}],
});That's it! The wallet handles authentication, user registration, and transaction signing automatically.
Configuration Options
Custom Client ID
const provider = await connectWallet({
clientId: 'your-client-id',
});Chain Selection
import {
connectWallet,
IMMUTABLE_ZKEVM_MAINNET_CHAIN,
IMMUTABLE_ZKEVM_TESTNET_CHAIN,
} from '@imtbl/wallet';
// Connect to mainnet
const provider = await connectWallet({
chains: [IMMUTABLE_ZKEVM_MAINNET_CHAIN],
});
// Connect to testnet (default)
const provider = await connectWallet({
chains: [IMMUTABLE_ZKEVM_TESTNET_CHAIN],
});Popup Overlay Options
const provider = await connectWallet({
popupOverlayOptions: {
disableGenericPopupOverlay: true,
disableBlockedPopupOverlay: true,
},
});Using External Auth Packages
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.
| Package | Use Case |
|---------|----------|
| @imtbl/auth-next-client | Next.js apps with server-side session management |
| @imtbl/auth | Non-Next.js apps needing custom auth control |
With @imtbl/auth-next-client (Next.js)
If you're using NextAuth for server-side session management, pass the getUser function from useImmutableSession:
Install dependencies:
npm install @imtbl/wallet @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5Integrate with the wallet:
'use client';
import { connectWallet } from '@imtbl/wallet';
import { useImmutableSession } from '@imtbl/auth-next-client';
import { useState } from 'react';
export function WalletConnect() {
const { getUser } = useImmutableSession();
const [provider, setProvider] = useState(null);
const handleConnect = async () => {
// Pass getUser to share session with the wallet
const walletProvider = await connectWallet({ getUser });
const accounts = await walletProvider.request({
method: 'eth_requestAccounts'
});
console.log('Connected:', accounts[0]);
setProvider(walletProvider);
};
return <button onClick={handleConnect}>Connect Wallet</button>;
}Benefits:
- Server-side session management with secure token storage
- Automatic token refresh via NextAuth callbacks
- SSR compatibility
- Single source of truth for authentication state
With @imtbl/auth (Direct)
For applications needing full control over authentication without NextAuth:
Install dependencies:
npm install @imtbl/wallet @imtbl/authIntegrate with the wallet:
import { connectWallet } from '@imtbl/wallet';
import { Auth } from '@imtbl/auth';
// Create your Auth instance
const auth = new Auth({
clientId: 'your-client-id',
redirectUri: 'https://your-app.com/callback',
// ... other options
});
// Create a getUser function that wraps your Auth instance
const getUser = async (forceRefresh?: boolean) => {
if (forceRefresh) {
return auth.forceUserRefresh();
}
return auth.getUser();
};
// Pass to connectWallet
const provider = await connectWallet({ getUser });When to use this approach:
- You need custom login/logout flows
- You want to manage auth state outside the wallet
- You're integrating with an existing auth system
API Reference
connectWallet(options?)
Creates an EIP-1193 compliant provider for Immutable zkEVM or other supported chains.
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| clientId | string | Auto-detected | Immutable client ID |
| chains | ChainConfig[] | [testnet, mainnet] | Chain configurations |
| initialChainId | number | First chain | Initial chain to connect to |
| popupOverlayOptions | PopupOverlayOptions | - | Options for login popup overlays |
| announceProvider | boolean | true | Whether to announce via EIP-6963 |
| feeTokenSymbol | string | 'IMX' | Preferred token symbol for relayer fees |
| jsonRpcReferrer | string | - | Referrer URL sent with JSON-RPC requests |
| crossSdkBridgeEnabled | boolean | false | Enable cross-SDK bridge mode |
| forceScwDeployBeforeMessageSignature | boolean | false | Force SCW deployment before message signature |
| getUser | GetUserFunction | Internal | Custom function to get user (for external auth integration) |
Returns
Promise<Provider> - An EIP-1193 compliant provider.
Provider Methods
The returned provider supports standard Ethereum JSON-RPC methods:
// Get accounts (triggers login if needed)
const accounts = await provider.request({ method: 'eth_requestAccounts' });
// Get current accounts (returns empty if not connected)
const accounts = await provider.request({ method: 'eth_accounts' });
// Get chain ID
const chainId = await provider.request({ method: 'eth_chainId' });
// Send transaction
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x...',
value: '0x...',
data: '0x...',
}],
});
// Sign message (personal_sign)
const signature = await provider.request({
method: 'personal_sign',
params: ['0x...message', '0x...address'],
});
// Sign typed data (EIP-712)
const signature = await provider.request({
method: 'eth_signTypedData_v4',
params: ['0x...address', JSON.stringify(typedData)],
});Chain Configuration
Chain ID Constants
import {
IMMUTABLE_ZKEVM_MAINNET_CHAIN_ID, // 13371
IMMUTABLE_ZKEVM_TESTNET_CHAIN_ID, // 13473
} from '@imtbl/wallet';Preset Chain Configurations
Immutable zkEVM
import {
IMMUTABLE_ZKEVM_MAINNET_CHAIN, // Mainnet config
IMMUTABLE_ZKEVM_TESTNET_CHAIN, // Testnet config
DEFAULT_CHAINS, // [testnet, mainnet]
} from '@imtbl/wallet';
// Use testnet only
const provider = await connectWallet({
chains: [IMMUTABLE_ZKEVM_TESTNET_CHAIN],
});
// Use mainnet only
const provider = await connectWallet({
chains: [IMMUTABLE_ZKEVM_MAINNET_CHAIN],
});
// Use both (default)
const provider = await connectWallet({
chains: DEFAULT_CHAINS,
});Chain Presets (Spread-friendly)
import {
IMMUTABLE_ZKEVM_MAINNET, // { chains: [mainnet] }
IMMUTABLE_ZKEVM_TESTNET, // { chains: [testnet] }
IMMUTABLE_ZKEVM_MULTICHAIN, // { chains: [testnet, mainnet] }
} from '@imtbl/wallet';
// Easy to spread into connectWallet
const provider = await connectWallet({
...IMMUTABLE_ZKEVM_MAINNET,
});Custom Chain Configuration
import type { ChainConfig } from '@imtbl/wallet';
const customChain: ChainConfig = {
chainId: 13473,
name: 'Immutable zkEVM Testnet',
rpcUrl: 'https://rpc.testnet.immutable.com',
relayerUrl: 'https://api.sandbox.immutable.com/relayer-mr',
apiUrl: 'https://api.sandbox.immutable.com',
passportDomain: 'https://passport.sandbox.immutable.com',
// Optional: Magic TEE config for dev environments
magicPublishableApiKey: 'pk_...',
magicProviderId: '...',
magicTeeBasePath: 'https://tee.express.magiclabs.com',
// Optional: Fee token (defaults to 'IMX')
feeTokenSymbol: 'IMX',
};Wallet Linking
Link external wallets (e.g., MetaMask, hardware wallets) to a user's Immutable Passport account.
Get Linked Addresses
Retrieve all wallet addresses linked to the current user's account:
import { getLinkedAddresses } from '@imtbl/wallet';
import { Auth } from '@imtbl/auth';
import { MultiRollupApiClients, createConfig } from '@imtbl/generated-clients';
const auth = new Auth({ /* ... */ });
const apiConfig = createConfig({ basePath: 'https://api.immutable.com' });
const apiClient = new MultiRollupApiClients({
indexer: apiConfig,
orderBook: apiConfig,
passport: apiConfig,
});
const linkedAddresses = await getLinkedAddresses(auth, apiClient);
console.log('Linked wallets:', linkedAddresses);
// ['0x1234...', '0x5678...']Link External Wallet
Link a new external wallet to the user's account:
import { linkExternalWallet } from '@imtbl/wallet';
import type { LinkWalletParams, LinkedWallet } from '@imtbl/wallet';
// 1. Get a signature from the external wallet
const walletAddress = '0x...'; // Address from MetaMask/external wallet
const nonce = 'unique-nonce-123'; // Generate a unique nonce
const message = `Link wallet ${walletAddress} with nonce ${nonce}`;
// Sign the message with the external wallet (e.g., using ethers.js or viem)
const signature = await externalWallet.signMessage(message);
// 2. Link the wallet
const params: LinkWalletParams = {
type: 'metamask', // Wallet type identifier
walletAddress,
signature,
nonce,
};
const linkedWallet: LinkedWallet = await linkExternalWallet(
auth,
apiClient,
params
);
console.log('Linked wallet:', linkedWallet);
// {
// address: '0x...',
// type: 'metamask',
// created_at: '2024-01-01T00:00:00Z',
// updated_at: '2024-01-01T00:00:00Z',
// clientName: 'your-client'
// }Linking Errors
The wallet linking API may return specific error codes:
| Error Code | Description |
|------------|-------------|
| ALREADY_LINKED | This wallet is already linked to an account |
| MAX_WALLETS_LINKED | Maximum number of linked wallets reached |
| DUPLICATE_NONCE | The nonce has already been used |
| VALIDATION_ERROR | Invalid signature or parameters |
EIP-6963 Provider Discovery
The wallet automatically announces itself via EIP-6963 for wallet discovery by dApps.
Automatic Announcement
By default, connectWallet announces the provider:
const provider = await connectWallet();
// Provider is automatically announced via EIP-6963Disable Announcement
const provider = await connectWallet({
announceProvider: false, // Don't announce via EIP-6963
});Manual Announcement
import { announceProvider, passportProviderInfo } from '@imtbl/wallet';
// Manually announce a provider
announceProvider({
info: passportProviderInfo,
provider: yourProvider,
});Provider Info
import { passportProviderInfo } from '@imtbl/wallet';
console.log(passportProviderInfo);
// {
// uuid: '...',
// name: 'Immutable Passport',
// icon: '...',
// rdns: 'com.immutable.passport'
// }Events
The provider emits standard EIP-1193 events:
// Account changes
provider.on('accountsChanged', (accounts: string[]) => {
console.log('Accounts changed:', accounts);
});
// Chain changes
provider.on('chainChanged', (chainId: string) => {
console.log('Chain changed:', chainId);
});
// Disconnection
provider.on('disconnect', (error: Error) => {
console.log('Disconnected:', error);
});
// Remove listener
provider.removeListener('accountsChanged', handler);Error Handling
WalletError
The SDK throws WalletError for wallet-related errors:
import { connectWallet, WalletError, WalletErrorType } from '@imtbl/wallet';
try {
const provider = await connectWallet();
const accounts = await provider.request({ method: 'eth_requestAccounts' });
} catch (error) {
if (error instanceof WalletError) {
switch (error.type) {
case WalletErrorType.NOT_LOGGED_IN_ERROR:
console.log('User is not logged in');
break;
case WalletErrorType.WALLET_CONNECTION_ERROR:
console.log('Failed to connect wallet:', error.message);
break;
case WalletErrorType.TRANSACTION_REJECTED:
console.log('User rejected the transaction');
break;
case WalletErrorType.UNAUTHORIZED:
console.log('Unauthorized - call eth_requestAccounts first');
break;
case WalletErrorType.GUARDIAN_ERROR:
console.log('Guardian validation failed');
break;
case WalletErrorType.INVALID_CONFIGURATION:
console.log('Invalid wallet configuration');
break;
case WalletErrorType.SERVICE_UNAVAILABLE_ERROR:
console.log('Service temporarily unavailable');
break;
default:
console.error('Wallet error:', error.message);
}
} else {
console.error('Unexpected error:', error);
}
}Error Types
| Error Type | Description |
|------------|-------------|
| NOT_LOGGED_IN_ERROR | User is not authenticated |
| WALLET_CONNECTION_ERROR | Failed to connect or link wallet |
| TRANSACTION_REJECTED | User rejected a transaction |
| UNAUTHORIZED | Operation requires authentication |
| GUARDIAN_ERROR | Guardian (security) validation failed |
| INVALID_CONFIGURATION | Invalid wallet configuration |
| SERVICE_UNAVAILABLE_ERROR | Backend service unavailable |
JSON-RPC Errors
For low-level RPC errors:
import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from '@imtbl/wallet';
// Standard provider error codes (EIP-1193)
ProviderErrorCode.USER_REJECTED_REQUEST // 4001
ProviderErrorCode.UNAUTHORIZED // 4100
ProviderErrorCode.UNSUPPORTED_METHOD // 4200
ProviderErrorCode.DISCONNECTED // 4900
// Standard RPC error codes
RpcErrorCode.INVALID_REQUEST // -32600
RpcErrorCode.METHOD_NOT_FOUND // -32601
RpcErrorCode.INVALID_PARAMS // -32602
RpcErrorCode.INTERNAL_ERROR // -32603TypeScript
Full TypeScript support is included:
import type {
// Connection options
ConnectWalletOptions,
ChainConfig,
PopupOverlayOptions,
// Provider types
Provider,
RequestArguments,
// User types
GetUserFunction,
User,
UserProfile,
UserZkEvm,
// Wallet linking
LinkWalletParams,
LinkedWallet,
// Transaction types
TypedDataPayload,
MetaTransaction,
RelayerTransaction,
RelayerTransactionStatus,
FeeOption,
// Event types
WalletEventMap,
ProviderEventMap,
AccountsChangedEvent,
// EIP-6963
EIP6963ProviderDetail,
EIP6963ProviderInfo,
} from '@imtbl/wallet';Advanced Usage
Direct Provider Access
For advanced use cases, you can access the provider classes directly:
import { ZkEvmProvider } from '@imtbl/wallet';Relayer Client
For direct relayer interaction:
import { RelayerClient } from '@imtbl/wallet';Wallet Helpers
Utility functions for wallet operations:
import * as walletHelpers from '@imtbl/wallet';Related Packages
Optional Auth Integrations
These packages are only needed if you want to manage authentication separately from the wallet:
| Package | Description |
|---------|-------------|
| @imtbl/auth-next-client | NextAuth client-side hooks for Next.js apps |
| @imtbl/auth-next-server | NextAuth server-side configuration |
| @imtbl/auth | Core authentication for custom auth flows |
Other Immutable Packages
| Package | Description |
|---------|-------------|
| @imtbl/generated-clients | API clients for Immutable services |
| @imtbl/metrics | Metrics and analytics utilities |
