Skip to content

Conversation

@alex-connolly
Copy link
Contributor

@alex-connolly alex-connolly commented Nov 13, 2025

Modular Passport SDK: @imtbl/auth and @imtbl/wallet

Refactors the monolithic @imtbl/passport package into two focused, tree-shakeable packages with simplified APIs and reduced bundle size.

Comparison

Auth Package (@imtbl/auth)

Metric Old (@imtbl/passport auth components) New (@imtbl/auth) Change
Source Files ~4 TypeScript files 1 TypeScript file -75%
Lines of Code ~760 (total) / ~585 (code only) ~182 (total) / ~135 (code only) -76% / -77%
Runtime Dependencies 19 (entire Passport package) 1 (oidc-client-ts) -95%
Bundle Size ~500KB+ (entire Passport) ~50KB (estimated, tree-shakeable) -90%

Wallet Package (@imtbl/wallet)

Metric Old (@imtbl/passport wallet components) New (@imtbl/wallet) Change
Source Files ~30 TypeScript files 23 TypeScript files -23%
Lines of Code ~3,041 (code only) ~2,427 (code only) -20%
Runtime Dependencies 19 (entire Passport package) 2 (viem, @imtbl/auth) -89%
Major Dependencies ethers, @0xsequence/*, magic-sdk, axios, uuid, jwt-decode, localforage viem, oidc-client-ts -71%
Bundle Size ~500KB+ (entire Passport) ~150KB (estimated, tree-shakeable) -70%

Note: LOC counts include comments and blank lines. Code-only counts exclude comments and blank lines.

Major Changes

Architecture

  • Modular split: @imtbl/auth (OAuth) and @imtbl/wallet (EIP-1193 provider)
  • No environments: Removed Environment enum; replaced with explicit ChainConfig[] arrays
  • Factory pattern: connectWallet() factory replaces class-based Passport instance
  • Automatic login: Login handled automatically when auth client provided (no explicit loginPopup() calls)
  • Wallet-only mode: Apps can connect to user wallets without requiring their own OAuth client (see Wallet-Only Mode Design below)

API Simplifications

  • Zero-config: connectWallet() works standalone with default chains
  • Progressive enhancement: Add auth client for authenticated features
  • Immutable provider: Provider configuration cannot be changed after creation (create new instance to change)
  • Stateless clients: RelayerClient, GuardianClient, ApiClient are stateless (no internal user/chain state)

Technical Improvements

  • Library-agnostic API: EIP-1193 provider works with both viem and ethers (clients choose their preferred library)
  • Tree-shakeable dependencies: viem dependency is tree-shakeable (only imports what's used)
  • Library usage: Uses viem internally for EVM operations, oidc-client-ts for OAuth
  • Bundle size: Significantly reduced through tree-shaking and modular design
  • EIP-6963: Provider announcement support for automatic wallet discovery
  • Cross-SDK bridge: New crossSdkBridgeEnabled mode for cross-SDK integration

Breaking Changes

  • No Environment: Must provide explicit ChainConfig objects
  • No Passport class: Use connectWallet() factory function
  • No connectEvm(): Provider returned directly from connectWallet()
  • No login() method: Login handled automatically when needed

Dependency Changes

Removed:

  • ethers → replaced with viem (smaller, tree-shakeable)
  • @0xsequence/abi, @0xsequence/core → custom minimal implementation
  • magic-sdk, @magic-ext/oidc, @magic-sdk/provider → removed (Magic TEE uses direct API calls)
  • axios → native fetch API
  • uuid → removed (EIP-6963 UUID is constant)
  • jwt-decode → removed (not needed)
  • localforage → removed (uses native localStorage)
  • @imtbl/x-client, @imtbl/x-provider → removed (IMX support removed)
  • @imtbl/config, @imtbl/metrics → removed (not used)

Added:

  • viem → modern EVM library (replaces ethers)

Wallet-Only Mode Design

Overview

Wallet-only mode allows apps to connect to user wallets without requiring their own OAuth client configuration. This enables a simpler onboarding experience where apps can interact with Passport wallets immediately, without needing to set up OAuth clients in Immutable Hub.

Problem Statement

Previously, apps needed to:

  1. Create an OAuth client in Immutable Hub
  2. Configure redirect URIs
  3. Provide clientId and redirectUri to the SDK
  4. Handle OAuth callbacks

This created friction for apps that only wanted wallet functionality and didn't need access to user profile data.

Solution

The SDK now supports wallet-only mode where:

  • Apps can call connectWallet() without providing an auth client
  • When eth_requestAccounts is called, the SDK automatically uses a shared OAuth client ID
  • Users authenticate via Passport's standard login popup
  • Apps receive a Provider interface but never see user profile/ID data
  • All wallet operations (transactions, signing) work normally with confirmation popups

Implementation Details

Shared OAuth Client

The SDK uses a shared OAuth client ID (immutable-passport-wallet-only) that:

  • Is configured by Immutable and available to all apps
  • Uses Passport-hosted redirect URI: https://passport.immutable.com/wallet-callback
  • Has limited scopes: openid transact (no profile or email access)
  • Allows apps to authenticate users without exposing user identity to the app

Authentication Flow

  1. App calls connectWallet() without auth:

    const provider = await connectWallet();
  2. App calls eth_requestAccounts:

    const accounts = await provider.request({ method: 'eth_requestAccounts' });
  3. SDK automatically:

    • Detects no auth client provided
    • Creates Auth instance with shared client ID (immutable-passport-wallet-only)
    • Checks for existing user session (via auth.getUser())
    • If no session exists, triggers Passport login popup
    • User authenticates via Immutable's standard OAuth flow
    • User object stored internally (never exposed to app)
    • Returns counterfactual wallet address to app
  4. App can now:

    • Call eth_sendTransaction (always requires confirmation popup)
    • Use all wallet features (signing, switching chains, etc.)
    • Never sees user profile/ID data

User Privacy

Apps using wallet-only mode:

  • Cannot access user profile data (email, name, etc.)
  • Cannot access user ID (sub claim)
  • Can only interact with the wallet (address, transactions, signing)
  • Must always show confirmation popups for transactions (enforced by Guardian API)

The User object is stored internally in the SDK but never exposed through the Provider interface, ensuring apps cannot access user identity information.

Backend Requirements

For wallet-only mode to work, the backend needs:

  1. OAuth Service: Create shared client ID immutable-passport-wallet-only with:

    • Redirect URI: https://passport.immutable.com/wallet-callback
    • Scopes: openid transact (no profile or email)
    • Passport must host callback page at that URL
  2. API Compatibility: All backend APIs must accept tokens from the shared client:

    • Passport API (/v2/passport/{chain}/counterfactual-address) - for wallet registration
    • Guardian API (/v1/transactions/evm/evaluate) - for transaction validation
    • Relayer API (/v1/transactions) - for transaction submission
    • Magic TEE API (/v1/wallet, /v1/wallet/sign/message) - for signing operations

Benefits

  1. Simplified Onboarding: Apps can start using Passport wallets immediately without OAuth setup
  2. Reduced Friction: No need to configure redirect URIs or manage OAuth clients
  3. Privacy-First: Apps don't get access to user identity, only wallet functionality
  4. Backward Compatible: Apps can still provide their own auth client for full access
  5. Same Security: All transactions still require confirmation popups via Guardian API

Migration Path

For apps that only need wallet functionality:

// Old: Required OAuth client setup
import { Auth } from '@imtbl/auth';
import { connectWallet } from '@imtbl/wallet';

const auth = new Auth({ clientId: '...', redirectUri: '...' });
const provider = await connectWallet({ auth });

// New: Wallet-only mode (no OAuth client needed)
import { connectWallet } from '@imtbl/wallet';

const provider = await connectWallet();
// SDK automatically handles authentication with shared client

For apps that need user profile access:

// Still works: Provide your own auth client
import { Auth } from '@imtbl/auth';
import { connectWallet } from '@imtbl/wallet';

const auth = new Auth({ 
  clientId: 'your-client-id', 
  redirectUri: 'https://your-app.com/callback',
  scope: 'openid profile email transact', // Full access
});
const provider = await connectWallet({ auth });

Migration Path

// Old
const passport = new Passport({
  baseConfig: { environment: Environment.PRODUCTION },
  clientId: '...',
  redirectUri: '...',
});
const provider = passport.connectEvm();

// New
import { Auth } from '@imtbl/auth';
import { connectWallet } from '@imtbl/wallet';

const auth = new Auth({ clientId: '...', redirectUri: '...' });
const provider = await connectWallet({ auth });

@nx-cloud
Copy link

nx-cloud bot commented Nov 13, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 9cfed46

Command Status Duration Result
nx affected -t build,lint,test ❌ Failed 4m 27s View ↗
nx run-many -p @imtbl/sdk,@imtbl/checkout-widge... ✅ Succeeded 1m 45s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-15 03:46:20 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants