From ff5633aed1b4e9c748f944449ba0666d5d7e0803 Mon Sep 17 00:00:00 2001 From: Alex Connolly <25735635+alex-connolly@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:02:20 +1100 Subject: [PATCH 1/5] initial wip --- pkg/auth/README.md | 134 +++ pkg/auth/package.json | 69 ++ pkg/auth/src/index.ts | 250 +++++ pkg/auth/tsconfig.json | 14 + pkg/auth/typedoc.json | 6 + pkg/wallet/README.md | 120 ++ pkg/wallet/jest.config.ts | 25 + pkg/wallet/package.json | 68 ++ pkg/wallet/src/api.ts | 73 ++ pkg/wallet/src/confirmation/confirmation.ts | 276 +++++ pkg/wallet/src/confirmation/overlay.ts | 308 ++++++ pkg/wallet/src/confirmation/popup.ts | 48 + pkg/wallet/src/confirmation/types.ts | 25 + pkg/wallet/src/eip6963.ts | 72 ++ pkg/wallet/src/ejection.ts | 75 ++ pkg/wallet/src/errors.ts | 27 + pkg/wallet/src/guardian.ts | 271 +++++ pkg/wallet/src/index.ts | 129 +++ pkg/wallet/src/metatransaction.ts | 194 ++++ pkg/wallet/src/provider.ts | 1083 +++++++++++++++++++ pkg/wallet/src/relayer.ts | 163 +++ pkg/wallet/src/sequence.ts | 305 ++++++ pkg/wallet/src/signer/magic.ts | 163 +++ pkg/wallet/src/signer/signer.ts | 23 + pkg/wallet/src/signer/signing.ts | 97 ++ pkg/wallet/src/utils/abi.ts | 17 + pkg/wallet/src/utils/chain.ts | 11 + pkg/wallet/src/utils/hex.ts | 54 + pkg/wallet/src/utils/http-client.ts | 74 ++ pkg/wallet/tsconfig.json | 14 + pkg/wallet/typedoc.json | 6 + pnpm-lock.yaml | 402 ++++++- pnpm-workspace.yaml | 2 + 33 files changed, 4564 insertions(+), 34 deletions(-) create mode 100644 pkg/auth/README.md create mode 100644 pkg/auth/package.json create mode 100644 pkg/auth/src/index.ts create mode 100644 pkg/auth/tsconfig.json create mode 100644 pkg/auth/typedoc.json create mode 100644 pkg/wallet/README.md create mode 100644 pkg/wallet/jest.config.ts create mode 100644 pkg/wallet/package.json create mode 100644 pkg/wallet/src/api.ts create mode 100644 pkg/wallet/src/confirmation/confirmation.ts create mode 100644 pkg/wallet/src/confirmation/overlay.ts create mode 100644 pkg/wallet/src/confirmation/popup.ts create mode 100644 pkg/wallet/src/confirmation/types.ts create mode 100644 pkg/wallet/src/eip6963.ts create mode 100644 pkg/wallet/src/ejection.ts create mode 100644 pkg/wallet/src/errors.ts create mode 100644 pkg/wallet/src/guardian.ts create mode 100644 pkg/wallet/src/index.ts create mode 100644 pkg/wallet/src/metatransaction.ts create mode 100644 pkg/wallet/src/provider.ts create mode 100644 pkg/wallet/src/relayer.ts create mode 100644 pkg/wallet/src/sequence.ts create mode 100644 pkg/wallet/src/signer/magic.ts create mode 100644 pkg/wallet/src/signer/signer.ts create mode 100644 pkg/wallet/src/signer/signing.ts create mode 100644 pkg/wallet/src/utils/abi.ts create mode 100644 pkg/wallet/src/utils/chain.ts create mode 100644 pkg/wallet/src/utils/hex.ts create mode 100644 pkg/wallet/src/utils/http-client.ts create mode 100644 pkg/wallet/tsconfig.json create mode 100644 pkg/wallet/typedoc.json diff --git a/pkg/auth/README.md b/pkg/auth/README.md new file mode 100644 index 0000000000..4a5c8902cb --- /dev/null +++ b/pkg/auth/README.md @@ -0,0 +1,134 @@ +# @imtbl/auth + +Minimal authentication package for Immutable Passport. Provides OAuth-based authentication that can be used standalone or passed to other SDK packages for enhanced functionality. + +## Installation + +```bash +npm install @imtbl/auth +# or +pnpm add @imtbl/auth +# or +yarn add @imtbl/auth +``` + +## Usage + +### Basic Authentication with Popup + +```typescript +import { Auth } from '@imtbl/auth'; + +const auth = new Auth({ + clientId: 'your-client-id', + redirectUri: 'https://your-app.com/callback', + environment: 'production', +}); + +// Login with popup +const user = await auth.loginPopup(); +console.log(user?.profile.email); +console.log(user?.access_token); // Direct access to tokens + +// Get current user +const currentUser = await auth.getUser(); +if (currentUser) { + console.log(currentUser.id_token); // Access ID token + console.log(currentUser.expired); // Check if expired +} + +// Logout +await auth.logout(); +``` + +### With Redirect Flow + +```typescript +// On your login page +await auth.loginRedirect(); + +// On your callback page +const user = await auth.handleRedirect(); +console.log(user?.profile.email); +console.log(user?.access_token); // Direct access to tokens +``` + +### Standalone Usage + +The auth package can be used completely independently: + +```typescript +import { Auth } from '@imtbl/auth'; + +const auth = new Auth({ clientId: '...', redirectUri: '...' }); +const user = await auth.loginPopup(); +const accessToken = user?.access_token; + +// Use token for API calls +fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, +}); +``` + +### With Wallet Package + +```typescript +import { Auth } from '@imtbl/auth'; +import { Wallet } from '@imtbl/wallet'; + +const auth = new Auth({ clientId: '...', redirectUri: '...' }); +await auth.loginPopup(); +const user = await auth.getUser(); + +// Pass authenticated user to wallet for enhanced features +const wallet = new Wallet({ authenticatedUser: user }); +const provider = await wallet.connect(); +``` + +## API Reference + +### `Auth` + +#### Constructor + +```typescript +new Auth(config: AuthConfig) +``` + +#### Methods + +- `loginPopup(options?: LoginOptions): Promise` - Login with popup window +- `loginRedirect(options?: LoginOptions): Promise` - Login with redirect flow +- `handleRedirect(): Promise` - Handle OAuth callback after redirect +- `getUser(): Promise` - Gets current authenticated user +- `logout(): Promise` - Logs out current user (with redirect) +- `logoutSilent(): Promise` - Logs out silently (without redirect) +- `refreshToken(): Promise` - Refreshes access token if expired + +### Types + +- `OidcUser` - OIDC user object from oidc-client-ts (includes `id_token`, `access_token`, `refresh_token`, `profile`, `expired`, `expires_at`, `scope`, etc.) +- `AuthConfig` - Configuration options +- `LoginOptions` - Login options (direct method, email, marketing consent) - works for both popup and redirect flows + +### Accessing Tokens + +Since methods return `OidcUser` directly, you can access tokens and user info: + +```typescript +const user = await auth.getUser(); +if (user) { + const accessToken = user.access_token; + const idToken = user.id_token; + const email = user.profile.email; + const isExpired = user.expired; + const expiresAt = user.expires_at; +} +``` + +## License + +Apache-2.0 + diff --git a/pkg/auth/package.json b/pkg/auth/package.json new file mode 100644 index 0000000000..12dbe3e386 --- /dev/null +++ b/pkg/auth/package.json @@ -0,0 +1,69 @@ +{ + "name": "@imtbl/auth", + "description": "Minimal authentication package for Immutable Passport", + "version": "0.0.0", + "author": "Immutable", + "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", + "dependencies": { + "@imtbl/config": "workspace:*", + "@imtbl/metrics": "workspace:*", + "oidc-client-ts": "3.3.0" + }, + "devDependencies": { + "@swc/core": "^1.3.36", + "@swc/jest": "^0.2.37", + "@types/jest": "^29.4.3", + "@types/node": "^18.14.2", + "@typescript-eslint/eslint-plugin": "^5.57.1", + "@typescript-eslint/parser": "^5.57.1", + "eslint": "^8.40.0", + "jest": "^29.4.3", + "jest-environment-jsdom": "^29.4.3", + "prettier": "^2.8.7", + "ts-node": "^10.9.1", + "tsup": "8.3.0", + "typescript": "^5.6.2" + }, + "engines": { + "node": ">=20.11.0" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "browser": "./dist/browser/index.js", + "require": "./dist/node/index.cjs", + "default": "./dist/node/index.js" + }, + "default": { + "types": "./dist/types/index.d.ts", + "browser": "./dist/browser/index.js", + "require": "./dist/node/index.cjs", + "default": "./dist/node/index.js" + } + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", + "license": "Apache-2.0", + "main": "dist/node/index.cjs", + "module": "dist/node/index.js", + "browser": "dist/browser/index.js", + "publishConfig": { + "access": "public" + }, + "repository": "immutable/ts-immutable-sdk.git", + "scripts": { + "build": "pnpm transpile && pnpm typegen", + "transpile": "tsup src/index.ts --config ../../tsup.config.js", + "typegen": "tsc --customConditions default --emitDeclarationOnly --outDir dist/types", + "pack:root": "pnpm pack --pack-destination $(dirname $(pnpm root -w))", + "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", + "test": "jest", + "test:watch": "jest --watch", + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" + }, + "type": "module", + "types": "./dist/types/index.d.ts" +} + diff --git a/pkg/auth/src/index.ts b/pkg/auth/src/index.ts new file mode 100644 index 0000000000..42a6879b4f --- /dev/null +++ b/pkg/auth/src/index.ts @@ -0,0 +1,250 @@ +/** + * @imtbl/auth - Minimal authentication package + * + * Provides OAuth-based authentication for Immutable Passport. + * Can be used standalone or passed to wallet/Passport clients for enhanced functionality. + */ + +import { + UserManager, + UserManagerSettings, + User, + WebStorageStateStore, + InMemoryWebStorage, +} from 'oidc-client-ts'; + +// Re-export User from oidc-client-ts as the main User type +export type { User } from 'oidc-client-ts'; + +/** + * Authentication configuration + */ +export interface AuthConfig { + /** OAuth client ID (required) */ + clientId: string; + /** OAuth redirect URI */ + redirectUri: string; + /** Optional popup redirect URI (defaults to redirectUri) */ + popupRedirectUri?: string; + /** Optional logout redirect URI */ + logoutRedirectUri?: string; + /** OAuth scope (defaults to 'openid profile email') */ + scope?: string; +} + +/** + * Login options + */ +export interface LoginOptions { + /** Direct login method (e.g., 'google', 'apple', 'email') */ + directLoginMethod?: string; + /** Email address (required when directLoginMethod is 'email') */ + email?: string; + /** Marketing consent status */ + marketingConsent?: 'opted_in' | 'unsubscribed'; +} + +/** + * Builds UserManagerSettings from AuthConfig + */ +function buildUserManagerSettings(config: AuthConfig): UserManagerSettings { + const authDomain = 'https://auth.immutable.com'; + + // Use localStorage in browser, InMemoryWebStorage for SSR + const store: Storage = typeof window !== 'undefined' + ? window.localStorage + : new InMemoryWebStorage(); + + const userStore = new WebStorageStateStore({ store }); + + // Build logout endpoint + const logoutEndpoint = '/v2/logout'; + const endSessionEndpoint = new URL(logoutEndpoint, authDomain.replace(/^(?:https?:\/\/)?(.*)/, 'https://$1')); + endSessionEndpoint.searchParams.set('client_id', config.clientId); + if (config.logoutRedirectUri) { + endSessionEndpoint.searchParams.set('returnTo', config.logoutRedirectUri); + } + + const settings: UserManagerSettings = { + authority: authDomain, + client_id: config.clientId, + redirect_uri: config.redirectUri, + popup_redirect_uri: config.popupRedirectUri || config.redirectUri, + scope: config.scope || 'openid profile email', + userStore, + metadata: { + authorization_endpoint: `${authDomain}/authorize`, + token_endpoint: `${authDomain}/oauth/token`, + userinfo_endpoint: `${authDomain}/userinfo`, + end_session_endpoint: endSessionEndpoint.toString(), + revocation_endpoint: `${authDomain}/oauth/revoke`, + }, + mergeClaimsStrategy: { array: 'merge' }, + automaticSilentRenew: false, + revokeTokenTypes: ['refresh_token'], + extraQueryParams: { audience: 'platform_api' }, + }; + + return settings; +} + +/** + * Builds extra query parameters for login + */ +function buildExtraQueryParams(options?: LoginOptions): Record { + const params: Record = {}; + + if (options?.directLoginMethod) { + params.direct = options.directLoginMethod; + if (options.directLoginMethod === 'email' && options.email) { + params.email = options.email; + } + } + + if (options?.marketingConsent) { + params.marketingConsent = options.marketingConsent; + } + + return params; +} + +/** + * Minimal authentication client + * + * @example + * ```typescript + * import { Auth } from '@imtbl/auth'; + * + * const auth = new Auth({ + * clientId: 'your-client-id', + * redirectUri: 'https://your-app.com/callback' + * }); + * + * // Login with popup + * const user = await auth.loginPopup(); + * console.log(user?.profile.email); + * + * // Or login with redirect + * await auth.loginRedirect(); + * // Then on callback page: + * const user = await auth.handleRedirect(); + * ``` + */ +export class Auth { + private userManager: UserManager; + + /** + * Creates a new Auth instance + */ + constructor(config: AuthConfig) { + if (!config.clientId) { + throw new Error('clientId is required'); + } + if (!config.redirectUri) { + throw new Error('redirectUri is required'); + } + + this.userManager = new UserManager(buildUserManagerSettings(config)); + } + + /** + * Initiates login with popup window + * @param options Optional login options + * @returns Promise resolving to User, or null if cancelled + */ + async loginPopup(options?: LoginOptions): Promise { + try { + await this.userManager.clearStaleState(); + + const extraQueryParams = buildExtraQueryParams(options); + const oidcUser = await this.userManager.signinPopup({ + extraQueryParams, + popupWindowFeatures: { + width: 410, + height: 450, + }, + }); + + return oidcUser; + } catch (error) { + // Return null if popup was closed by user, otherwise pass through error + if (error instanceof Error && error.message.includes('Popup closed')) { + return null; + } + throw error; + } + } + + /** + * Initiates login with redirect + * @param options Optional login options + */ + async loginRedirect(options?: LoginOptions): Promise { + await this.userManager.clearStaleState(); + + const extraQueryParams = buildExtraQueryParams(options); + await this.userManager.signinRedirect({ extraQueryParams }); + } + + /** + * Handles OAuth callback after redirect + * Call this on your callback page after loginRedirect() + * @returns Promise resolving to User, or null if no user + */ + async handleRedirect(): Promise { + const oidcUser = await this.userManager.signinCallback(); + return oidcUser || null; + } + + /** + * Gets the current authenticated user + * @returns Promise resolving to User, or null if not authenticated + */ + async getUser(): Promise { + try { + const oidcUser = await this.userManager.getUser(); + if (!oidcUser || oidcUser.expired) { + return null; + } + return oidcUser; + } catch (error) { + // If user not found, return null (not an error) + if (error instanceof Error && error.message.includes('user not found')) { + return null; + } + // Pass through other errors - they're already well-formed + throw error; + } + } + + /** + * Logs out the current user + */ + async logout(): Promise { + // Pass through errors - oidc-client-ts errors are already clear + await this.userManager.signoutRedirect(); + } + + /** + * Logs out silently (without redirect) + */ + async logoutSilent(): Promise { + // Pass through errors - oidc-client-ts errors are already clear + await this.userManager.signoutSilent(); + } + + /** + * Refreshes the access token if expired + * Called automatically by oidc-client-ts when needed, but can be called manually + */ + async refreshToken(): Promise { + const oidcUser = await this.userManager.getUser(); + if (!oidcUser) { + throw new Error('No user to refresh'); + } + + if (oidcUser.expired) { + await this.userManager.signinSilent(); + } + } +} diff --git a/pkg/auth/tsconfig.json b/pkg/auth/tsconfig.json new file mode 100644 index 0000000000..b97dfb1ce4 --- /dev/null +++ b/pkg/auth/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDirs": ["src"], + "customConditions": ["development"] + }, + "include": ["src"], + "exclude": [ + "node_modules", + "dist" + ] +} + diff --git a/pkg/auth/typedoc.json b/pkg/auth/typedoc.json new file mode 100644 index 0000000000..a2a44eaecf --- /dev/null +++ b/pkg/auth/typedoc.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "name": "auth" +} + diff --git a/pkg/wallet/README.md b/pkg/wallet/README.md new file mode 100644 index 0000000000..69ee27de26 --- /dev/null +++ b/pkg/wallet/README.md @@ -0,0 +1,120 @@ +# @imtbl/wallet + +Minimal wallet package for Immutable zkEVM. Provides an EIP-1193 compatible provider that can be used standalone (zero config) or with authenticated user for enhanced functionality. + +## Installation + +```bash +npm install @imtbl/wallet +# or +pnpm add @imtbl/wallet +# or +yarn add @imtbl/wallet +``` + +## Usage + +### Zero Config (Standalone) + +Works immediately without any configuration: + +```typescript +import { Wallet } from '@imtbl/wallet'; + +const wallet = new Wallet(); +const provider = await wallet.connect(); + +// Use with viem +import { createWalletClient, custom } from 'viem'; +const client = createWalletClient({ + transport: custom(provider), +}); + +// Use with ethers +import { BrowserProvider } from 'ethers'; +const ethersProvider = new BrowserProvider(provider); +``` + +### With Authentication (Enhanced Features) + +Pass an authenticated user from `@imtbl/auth` for enhanced features: + +```typescript +import { Auth } from '@imtbl/auth'; +import { Wallet } from '@imtbl/wallet'; + +// Authenticate first +const auth = new Auth({ clientId: '...', redirectUri: '...' }); +await auth.login(); +const user = await auth.getUser(); + +// Create wallet with auth context +const wallet = new Wallet({ authenticatedUser: user }); +const provider = await wallet.connect(); + +// Now has enhanced features (user context, linked wallets via Passport) +``` + +### Basic Wallet Operations + +```typescript +const wallet = new Wallet(); +const provider = await wallet.connect(); + +// Get wallet address +const address = await wallet.getAddress(); + +// Get chain ID +const chainId = await wallet.getChainId(); + +// Check connection +const isConnected = await wallet.isConnected(); + +// Use EIP-1193 methods +const accounts = await provider.request({ method: 'eth_requestAccounts' }); +const balance = await provider.request({ + method: 'eth_getBalance', + params: [accounts[0], 'latest'], +}); +``` + +## API Reference + +### `Wallet` + +#### Constructor + +```typescript +new Wallet(config?: WalletConfig) +``` + +If `config` is omitted, works in zero-config mode. Pass `authenticatedUser` for enhanced features. + +#### Methods + +- `connect(options?: ConnectOptions): Promise` - Connects and returns EIP-1193 provider +- `getAddress(): Promise` - Gets connected wallet address +- `getChainId(): Promise` - Gets current chain ID +- `isConnected(): Promise` - Checks if wallet is connected + +### Types + +- `Provider` - EIP-1193 compatible provider interface +- `RequestArguments` - JSON-RPC request format +- `WalletConfig` - Optional configuration +- `ConnectOptions` - Connection options +- `AuthenticatedUser` - Authenticated user from `@imtbl/auth` + +## EIP-1193 Compatibility + +The provider returned by `connect()` is fully EIP-1193 compatible and works with: + +- **viem** - `createWalletClient({ transport: custom(provider) })` +- **ethers** - `new BrowserProvider(provider)` +- **web3.js** - `new Web3(provider)` +- Any library that supports EIP-1193 + +## License + +Apache-2.0 + diff --git a/pkg/wallet/jest.config.ts b/pkg/wallet/jest.config.ts new file mode 100644 index 0000000000..199283cca3 --- /dev/null +++ b/pkg/wallet/jest.config.ts @@ -0,0 +1,25 @@ +import type { Config } from 'jest'; +import { execSync } from 'child_process'; +import { name } from './package.json'; + +const rootDirs = execSync(`pnpm --filter ${name}... exec pwd`) + .toString() + .split('\n') + .filter(Boolean) + .map((dir) => `${dir}/dist`); + +const config: Config = { + clearMocks: true, + roots: ['/src', ...rootDirs], + coverageProvider: 'v8', + moduleDirectories: ['node_modules', 'src'], + moduleNameMapper: { '^@imtbl/(.*)$': '/../../node_modules/@imtbl/$1/src' }, + testEnvironment: 'jsdom', + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, + transformIgnorePatterns: [], +}; + +export default config; + diff --git a/pkg/wallet/package.json b/pkg/wallet/package.json new file mode 100644 index 0000000000..faba17f3ff --- /dev/null +++ b/pkg/wallet/package.json @@ -0,0 +1,68 @@ +{ + "name": "@imtbl/wallet", + "description": "Minimal wallet package for Immutable zkEVM - EIP-1193 compatible provider", + "version": "0.0.0", + "author": "Immutable", + "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", + "dependencies": { + "@imtbl/auth": "workspace:*", + "viem": "^2.39.0" + }, + "devDependencies": { + "@swc/core": "^1.3.36", + "@swc/jest": "^0.2.37", + "@types/jest": "^29.4.3", + "@types/node": "^18.14.2", + "@typescript-eslint/eslint-plugin": "^5.57.1", + "@typescript-eslint/parser": "^5.57.1", + "eslint": "^8.40.0", + "jest": "^29.4.3", + "jest-environment-jsdom": "^29.4.3", + "prettier": "^2.8.7", + "ts-node": "^10.9.1", + "tsup": "8.3.0", + "typescript": "^5.6.2" + }, + "engines": { + "node": ">=20.11.0" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "browser": "./dist/browser/index.js", + "require": "./dist/node/index.cjs", + "default": "./dist/node/index.js" + }, + "default": { + "types": "./dist/types/index.d.ts", + "browser": "./dist/browser/index.js", + "require": "./dist/node/index.cjs", + "default": "./dist/node/index.js" + } + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", + "license": "Apache-2.0", + "main": "dist/node/index.cjs", + "module": "dist/node/index.js", + "browser": "dist/browser/index.js", + "publishConfig": { + "access": "public" + }, + "repository": "immutable/ts-immutable-sdk.git", + "scripts": { + "build": "pnpm transpile && pnpm typegen", + "transpile": "tsup src/index.ts --config ../../tsup.config.js", + "typegen": "tsc --customConditions default --emitDeclarationOnly --outDir dist/types", + "pack:root": "pnpm pack --pack-destination $(dirname $(pnpm root -w))", + "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", + "test": "jest", + "test:watch": "jest --watch", + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" + }, + "type": "module", + "types": "./dist/types/index.d.ts" +} + diff --git a/pkg/wallet/src/api.ts b/pkg/wallet/src/api.ts new file mode 100644 index 0000000000..dd4e3b416d --- /dev/null +++ b/pkg/wallet/src/api.ts @@ -0,0 +1,73 @@ +/** + * Minimal API client for Passport APIs + */ + +import type { User } from '@imtbl/auth'; +import { getEip155ChainId } from './utils/chain'; +import { authenticatedFetch } from './utils/http-client'; + +export interface ApiClientConfig { + apiUrl: string; +} + +/** + * Minimal API client + */ +export class ApiClient { + private config: ApiClientConfig; + + constructor(config: ApiClientConfig) { + this.config = config; + } + + /** + * Lists available chains + */ + async listChains(): Promise> { + const data = await authenticatedFetch<{ result?: Array<{ id: string; name: string }> }>( + `${this.config.apiUrl}/v1/chains` + ); + return data.result || []; + } + + /** + * Creates a counterfactual address (registers user) + */ + async createCounterfactualAddress( + chainName: string, + ethereumAddress: string, + ethereumSignature: string, + user: User + ): Promise { + // User is guaranteed to be authenticated when this is called + // (ensured by ensureAuthenticated() in provider) + // Trust provider - use access_token directly + + const data = await authenticatedFetch<{ counterfactual_address: string }>( + `${this.config.apiUrl}/v2/passport/${chainName}/counterfactual-address`, + { + method: 'POST', + body: { + ethereum_address: ethereumAddress, + ethereum_signature: ethereumSignature, + }, + token: user.access_token, + } + ); + return data.counterfactual_address; + } + + /** + * Gets chain name from chain ID + */ + async getChainName(chainId: number): Promise { + const chains = await this.listChains(); + const eipChainId = getEip155ChainId(chainId); + const chain = chains.find(c => c.id === eipChainId); + if (!chain) { + throw new Error(`Chain ${chainId} not found`); + } + return chain.name; + } +} + diff --git a/pkg/wallet/src/confirmation/confirmation.ts b/pkg/wallet/src/confirmation/confirmation.ts new file mode 100644 index 0000000000..8f47427aed --- /dev/null +++ b/pkg/wallet/src/confirmation/confirmation.ts @@ -0,0 +1,276 @@ +/** + * Confirmation screen component + * Handles popup windows for transaction and message confirmations + */ + +import { + ConfirmationResult, + PASSPORT_CONFIRMATION_EVENT_TYPE, + ConfirmationReceiveMessage, + ConfirmationSendMessage, + MessageType, +} from './types'; +import { openPopupCenter } from './popup'; +import { ConfirmationOverlay, type PopupOverlayOptions } from './overlay'; + +const CONFIRMATION_WINDOW_TITLE = 'Confirm this transaction'; +const CONFIRMATION_WINDOW_HEIGHT = 720; +const CONFIRMATION_WINDOW_WIDTH = 480; +const CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION = 1000; + +type MessageHandler = (arg0: MessageEvent) => void; + +export interface ConfirmationScreenConfig { + /** Passport domain for confirmation URLs */ + passportDomain: string; + /** Overlay options */ + popupOverlayOptions?: PopupOverlayOptions; +} + +/** + * Confirmation screen component + */ +export class ConfirmationScreen { + private config: ConfirmationScreenConfig; + private confirmationWindow: Window | undefined; + private popupOptions: { width: number; height: number } | undefined; + private overlay: ConfirmationOverlay | undefined; + private overlayClosed: boolean; + private timer: ReturnType | undefined; + + constructor(config: ConfirmationScreenConfig) { + this.config = config; + this.overlayClosed = false; + } + + private getHref(relativePath: string, queryStringParams?: { [key: string]: any }) { + let href = `${this.config.passportDomain}/transaction-confirmation/${relativePath}`; + + if (queryStringParams) { + const queryString = Object.keys(queryStringParams) + .map((key) => `${key}=${queryStringParams[key]}`) + .join('&'); + href = `${href}?${queryString}`; + } + + return href; + } + + /** + * Request confirmation for a transaction + */ + requestConfirmation( + transactionId: string, + etherAddress: string, + chainId: string, + ): Promise { + return new Promise((resolve, reject) => { + const messageHandler = ({ data, origin }: MessageEvent) => { + if ( + origin !== this.config.passportDomain + || data.eventType !== PASSPORT_CONFIRMATION_EVENT_TYPE + ) { + return; + } + + switch (data.messageType as ConfirmationReceiveMessage) { + case ConfirmationReceiveMessage.CONFIRMATION_WINDOW_READY: { + this.confirmationWindow?.postMessage({ + eventType: PASSPORT_CONFIRMATION_EVENT_TYPE, + messageType: ConfirmationSendMessage.CONFIRMATION_START, + }, this.config.passportDomain); + break; + } + case ConfirmationReceiveMessage.TRANSACTION_CONFIRMED: { + this.closeWindow(); + resolve({ confirmed: true }); + break; + } + case ConfirmationReceiveMessage.TRANSACTION_REJECTED: { + this.closeWindow(); + resolve({ confirmed: false }); + break; + } + case ConfirmationReceiveMessage.TRANSACTION_ERROR: { + this.closeWindow(); + reject(new Error('Error during transaction confirmation')); + break; + } + default: + this.closeWindow(); + reject(new Error('Unsupported message type')); + } + }; + + const href = this.getHref('zkevm/transaction', { + transactionID: transactionId, + etherAddress, + chainType: 'evm', + chainID: chainId, + }); + window.addEventListener('message', messageHandler); + this.showConfirmationScreen(href, messageHandler, resolve); + }); + } + + /** + * Request confirmation for a message + */ + requestMessageConfirmation( + messageID: string, + etherAddress: string, + messageType?: MessageType, + ): Promise { + return new Promise((resolve, reject) => { + const messageHandler = ({ data, origin }: MessageEvent) => { + if ( + origin !== this.config.passportDomain + || data.eventType !== PASSPORT_CONFIRMATION_EVENT_TYPE + ) { + return; + } + switch (data.messageType as ConfirmationReceiveMessage) { + case ConfirmationReceiveMessage.CONFIRMATION_WINDOW_READY: { + this.confirmationWindow?.postMessage({ + eventType: PASSPORT_CONFIRMATION_EVENT_TYPE, + messageType: ConfirmationSendMessage.CONFIRMATION_START, + }, this.config.passportDomain); + break; + } + case ConfirmationReceiveMessage.MESSAGE_CONFIRMED: { + this.closeWindow(); + resolve({ confirmed: true }); + break; + } + case ConfirmationReceiveMessage.MESSAGE_REJECTED: { + this.closeWindow(); + resolve({ confirmed: false }); + break; + } + case ConfirmationReceiveMessage.MESSAGE_ERROR: { + this.closeWindow(); + reject(new Error('Error during message confirmation')); + break; + } + default: + this.closeWindow(); + reject(new Error('Unsupported message type')); + } + }; + + window.addEventListener('message', messageHandler); + const href = this.getHref('zkevm/message', { + messageID, + etherAddress, + ...(messageType ? { messageType } : {}), + }); + this.showConfirmationScreen(href, messageHandler, resolve); + }); + } + + /** + * Show loading screen + */ + loading(popupOptions?: { width: number; height: number }) { + this.popupOptions = popupOptions; + + try { + this.confirmationWindow = openPopupCenter({ + url: this.getHref('loading'), + title: CONFIRMATION_WINDOW_TITLE, + width: popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + this.overlay = new ConfirmationOverlay( + this.config.popupOverlayOptions || {}, + false + ); + } catch (error) { + // If an error is thrown here then the popup is blocked + this.overlay = new ConfirmationOverlay( + this.config.popupOverlayOptions || {}, + true + ); + } + + this.overlay.append( + () => { + try { + this.confirmationWindow?.close(); + this.confirmationWindow = openPopupCenter({ + url: this.getHref('loading'), + title: CONFIRMATION_WINDOW_TITLE, + width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + } catch { /* Empty */ } + }, + () => { + this.overlayClosed = true; + this.closeWindow(); + }, + ); + } + + /** + * Close the confirmation window + */ + closeWindow() { + this.confirmationWindow?.close(); + this.overlay?.remove(); + this.overlay = undefined; + } + + /** + * Show confirmation screen + */ + private showConfirmationScreen(href: string, messageHandler: MessageHandler, resolve: Function) { + // If popup blocked, the confirmation window will not exist + if (this.confirmationWindow) { + this.confirmationWindow.location.href = href; + } + + // This indicates the user closed the overlay so the transaction should be rejected + if (!this.overlay) { + this.overlayClosed = false; + resolve({ confirmed: false }); + return; + } + + // Poll for window close + const timerCallback = () => { + if (this.confirmationWindow?.closed || this.overlayClosed) { + clearInterval(this.timer); + window.removeEventListener('message', messageHandler); + resolve({ confirmed: false }); + this.overlayClosed = false; + this.confirmationWindow = undefined; + } + }; + this.timer = setInterval( + timerCallback, + CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION, + ); + this.overlay.update(() => this.recreateConfirmationWindow(href, timerCallback)); + } + + /** + * Recreate confirmation window + */ + private recreateConfirmationWindow(href: string, timerCallback: () => void) { + try { + // Clears and recreates the timer to ensure when the confirmation window + // is closed and recreated the transaction is not rejected. + clearInterval(this.timer); + this.confirmationWindow?.close(); + this.confirmationWindow = openPopupCenter({ + url: href, + title: CONFIRMATION_WINDOW_TITLE, + width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + this.timer = setInterval(timerCallback, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION); + } catch { /* Empty */ } + } +} + diff --git a/pkg/wallet/src/confirmation/overlay.ts b/pkg/wallet/src/confirmation/overlay.ts new file mode 100644 index 0000000000..2749d224a4 --- /dev/null +++ b/pkg/wallet/src/confirmation/overlay.ts @@ -0,0 +1,308 @@ +/** + * Confirmation overlay component + * Shows overlay when popup is blocked or needs help + */ + +export interface PopupOverlayOptions { + disableGenericPopupOverlay?: boolean; + disableBlockedPopupOverlay?: boolean; +} + +const PASSPORT_OVERLAY_ID = 'passport-overlay'; +const PASSPORT_OVERLAY_CONTENTS_ID = 'passport-overlay-contents'; +const PASSPORT_OVERLAY_CLOSE_ID = `${PASSPORT_OVERLAY_ID}-close`; +const PASSPORT_OVERLAY_TRY_AGAIN_ID = `${PASSPORT_OVERLAY_ID}-try-again`; + +const CLOSE_BUTTON_SVG = ` + + + +`; + +const POPUP_BLOCKED_SVG = ` + + + +`; + +const IMMUTABLE_LOGO_SVG = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +const getCloseButton = (): string => ` + +`; + +const getTryAgainButton = () => ` + +`; + +const getBlockedContents = () => ` + ${IMMUTABLE_LOGO_SVG} +
+ ${POPUP_BLOCKED_SVG} + Pop-up blocked +
+

+ Please try again below.
+ If the problem continues, adjust your
+ browser settings. +

+ ${getTryAgainButton()} +`; + +const getGenericContents = () => ` + ${IMMUTABLE_LOGO_SVG} +

+ Secure pop-up not showing?
We'll help you re-launch +

+ ${getTryAgainButton()} +`; + +const getOverlay = (contents: string): string => ` +
+ ${getCloseButton()} +
+ ${contents ?? ''} +
+
+`; + +export const getBlockedOverlay = () => getOverlay(getBlockedContents()); +export const getGenericOverlay = () => getOverlay(getGenericContents()); + +/** + * Confirmation overlay component + */ +export class ConfirmationOverlay { + private disableGenericPopupOverlay: boolean; + private disableBlockedPopupOverlay: boolean; + private overlay: HTMLDivElement | undefined; + private isBlockedOverlay: boolean; + private tryAgainListener: (() => void) | undefined; + private onCloseListener: (() => void) | undefined; + + constructor(popupOverlayOptions: PopupOverlayOptions, isBlockedOverlay: boolean = false) { + this.disableBlockedPopupOverlay = popupOverlayOptions.disableBlockedPopupOverlay || false; + this.disableGenericPopupOverlay = popupOverlayOptions.disableGenericPopupOverlay || false; + this.isBlockedOverlay = isBlockedOverlay; + } + + append(tryAgainOnClick: () => void, onCloseClick: () => void) { + if (this.isBlockedOverlay && this.disableBlockedPopupOverlay) { + return; + } + if (!this.isBlockedOverlay && this.disableGenericPopupOverlay) { + return; + } + + this.tryAgainListener = tryAgainOnClick; + this.onCloseListener = onCloseClick; + + const overlayHTML = this.isBlockedOverlay ? getBlockedOverlay() : getGenericOverlay(); + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = overlayHTML; + this.overlay = tempDiv.firstChild as HTMLDivElement; + + document.body.appendChild(this.overlay); + + // Attach event listeners + const closeButton = document.getElementById(PASSPORT_OVERLAY_CLOSE_ID); + const tryAgainButton = document.getElementById(PASSPORT_OVERLAY_TRY_AGAIN_ID); + + if (closeButton) { + closeButton.addEventListener('click', () => { + this.remove(); + if (this.onCloseListener) { + this.onCloseListener(); + } + }); + } + + if (tryAgainButton) { + tryAgainButton.addEventListener('click', () => { + if (this.tryAgainListener) { + this.tryAgainListener(); + } + }); + } + } + + update(tryAgainOnClick: () => void) { + const tryAgainButton = document.getElementById(PASSPORT_OVERLAY_TRY_AGAIN_ID); + if (tryAgainButton) { + tryAgainButton.replaceWith( + Object.assign(document.createElement('button'), { + innerHTML: getTryAgainButton().match(/]*>([\s\S]*?)<\/button>/)![1], + onclick: tryAgainOnClick, + }) + ); + } + } + + remove() { + if (this.overlay) { + this.overlay.remove(); + this.overlay = undefined; + } + } +} + diff --git a/pkg/wallet/src/confirmation/popup.ts b/pkg/wallet/src/confirmation/popup.ts new file mode 100644 index 0000000000..8e04b2aa22 --- /dev/null +++ b/pkg/wallet/src/confirmation/popup.ts @@ -0,0 +1,48 @@ +/** + * Popup utility functions + */ + +export interface PopUpProps { + url: string; + title: string; + width: number; + height: number; +} + +/** + * Opens a popup window centered on screen + */ +export const openPopupCenter = ({ + url, + title, + width, + height, +}: PopUpProps): Window => { + const left = Math.max( + 0, + Math.round(window.screenX + (window.outerWidth - width) / 2), + ); + const top = Math.max( + 0, + Math.round(window.screenY + (window.outerHeight - height) / 2), + ); + + const newWindow = window.open( + url, + title, + ` + scrollbars=yes, + width=${width}, + height=${height}, + top=${top}, + left=${left} + `, + ); + if (!newWindow) { + throw new Error('Failed to open confirmation screen'); + } + + newWindow.focus(); + return newWindow; +}; + diff --git a/pkg/wallet/src/confirmation/types.ts b/pkg/wallet/src/confirmation/types.ts new file mode 100644 index 0000000000..1eecbe0b1c --- /dev/null +++ b/pkg/wallet/src/confirmation/types.ts @@ -0,0 +1,25 @@ +/** + * Confirmation screen types + */ + +export enum ConfirmationSendMessage { + CONFIRMATION_START = 'confirmation_start', +} + +export enum ConfirmationReceiveMessage { + CONFIRMATION_WINDOW_READY = 'confirmation_window_ready', + TRANSACTION_CONFIRMED = 'transaction_confirmed', + TRANSACTION_ERROR = 'transaction_error', + TRANSACTION_REJECTED = 'transaction_rejected', + MESSAGE_CONFIRMED = 'message_confirmed', + MESSAGE_ERROR = 'message_error', + MESSAGE_REJECTED = 'message_rejected', +} + +export type ConfirmationResult = { + confirmed: boolean; +}; + +export const PASSPORT_CONFIRMATION_EVENT_TYPE = 'imx_passport_confirmation'; + +export type MessageType = 'erc191' | 'eip712'; diff --git a/pkg/wallet/src/eip6963.ts b/pkg/wallet/src/eip6963.ts new file mode 100644 index 0000000000..77cb86507c --- /dev/null +++ b/pkg/wallet/src/eip6963.ts @@ -0,0 +1,72 @@ +/** + * EIP-6963 Provider Announcement + * Allows dApps to discover Passport wallet automatically + */ + +import type { Provider } from './provider'; + +/** + * EIP-6963 Provider Info + */ +export interface EIP6963ProviderInfo { + icon: `data:image/${string}`; // RFC-2397 + name: string; + rdns: string; + uuid: string; +} + +/** + * EIP-6963 Provider Detail + */ +export interface EIP6963ProviderDetail { + info: EIP6963ProviderInfo; + provider: Provider; +} + +/** + * EIP-6963 Announce Provider Event + */ +export interface EIP6963AnnounceProviderEvent extends CustomEvent { + type: 'eip6963:announceProvider'; +} + +/** + * Generate a UUID v4 (simple implementation without external dependency) + */ +function generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} + +/** + * Passport provider info for EIP-6963 + */ +export const passportProviderInfo: EIP6963ProviderInfo = { + // eslint-disable-next-line max-len + icon: 'data:image/svg+xml,', + name: 'Immutable Passport', + rdns: 'com.immutable.passport', + uuid: generateUUID(), +}; + +/** + * Announces the provider via EIP-6963 + */ +export function announceProvider(detail: EIP6963ProviderDetail): void { + if (typeof window === 'undefined') return; + + const event: CustomEvent = new CustomEvent( + 'eip6963:announceProvider', + { detail: Object.freeze(detail) }, + ) as EIP6963AnnounceProviderEvent; + + window.dispatchEvent(event); + + // Listen for requests and re-announce + const handler = () => window.dispatchEvent(event); + window.addEventListener('eip6963:requestProvider', handler); +} + diff --git a/pkg/wallet/src/ejection.ts b/pkg/wallet/src/ejection.ts new file mode 100644 index 0000000000..77fea14cba --- /dev/null +++ b/pkg/wallet/src/ejection.ts @@ -0,0 +1,75 @@ +import { JsonRpcError, RpcErrorCode } from './errors'; +import type { TransactionRequest } from './metatransaction'; +import { buildMetaTransaction } from './metatransaction'; +import { signMetaTransactions } from './sequence'; +import type { Signer } from './signer/signer'; + +/** + * Ejection transaction response + */ +export interface EjectionTransactionResponse { + to: string; + data: string; + chainId: string; +} + +/** + * Prepares and signs ejection transaction + * Ejection transactions are simpler - no fee, no Guardian validation, no relayer + * Just sign the transaction and return it for the user to submit elsewhere + */ +export async function prepareAndSignEjectionTransaction({ + transactionRequest, + ethSigner, + zkEvmAddress, + chainId, +}: { + transactionRequest: TransactionRequest; + ethSigner: Signer; + zkEvmAddress: string; + chainId: number; +}): Promise { + // Validate required fields + if (!transactionRequest.to) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a "to" field' + ); + } + + if (typeof transactionRequest.nonce === 'undefined') { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a "nonce" field' + ); + } + + if (!transactionRequest.chainId) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a "chainId" field' + ); + } + + // Convert to MetaTransaction (same as normal transactions) + const metaTransaction = buildMetaTransaction(transactionRequest); + const nonce = typeof transactionRequest.nonce === 'number' + ? BigInt(transactionRequest.nonce) + : transactionRequest.nonce!; + const chainIdBigInt = BigInt(chainId); + + // Sign the transaction + const signedTransaction = await signMetaTransactions( + [metaTransaction], + nonce, + chainIdBigInt, + zkEvmAddress, + ethSigner + ); + + return { + to: zkEvmAddress, + data: signedTransaction, + chainId: `eip155:${chainId}`, + }; +} \ No newline at end of file diff --git a/pkg/wallet/src/errors.ts b/pkg/wallet/src/errors.ts new file mode 100644 index 0000000000..2f4ac20d46 --- /dev/null +++ b/pkg/wallet/src/errors.ts @@ -0,0 +1,27 @@ +/** + * JSON-RPC error codes + * @see https://eips.ethereum.org/EIPS/eip-1193#provider-errors + * @see https://eips.ethereum.org/EIPS/eip-1474#error-codes + */ + +export enum ProviderErrorCode { + UNAUTHORIZED = 4100, + UNSUPPORTED_METHOD = 4200, +} + +export enum RpcErrorCode { + RPC_SERVER_ERROR = -32000, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + TRANSACTION_REJECTED = -32003, +} + +export class JsonRpcError extends Error { + public readonly code: ProviderErrorCode | RpcErrorCode; + + constructor(code: ProviderErrorCode | RpcErrorCode, message: string) { + super(message); + this.name = 'JsonRpcError'; + this.code = code; + } +} diff --git a/pkg/wallet/src/guardian.ts b/pkg/wallet/src/guardian.ts new file mode 100644 index 0000000000..6c35ace826 --- /dev/null +++ b/pkg/wallet/src/guardian.ts @@ -0,0 +1,271 @@ +/** + * Minimal guardian client for transaction evaluation + * No ethers dependency - uses native fetch + */ + +import type { User } from '@imtbl/auth'; +import { JsonRpcError, RpcErrorCode } from './errors'; +import { getEip155ChainId } from './utils/chain'; +import type { TypedDataPayload } from './relayer'; +import { ConfirmationScreen } from './confirmation/confirmation'; +import { authenticatedFetch } from './utils/http-client'; + +export interface GuardianClientConfig { + guardianUrl: string; + /** Confirmation screen for showing confirmation UI */ + confirmationScreen: ConfirmationScreen; +} + +/** + * Guardian API response types + */ +interface MessageEvaluationResponse { + confirmationRequired: boolean; + messageId?: string; +} + +interface TransactionEvaluationResponse { + confirmationRequired: boolean; + transactionId?: string; +} + +/** + * Minimal guardian client + */ +export class GuardianClient { + private config: GuardianClientConfig; + + constructor(config: GuardianClientConfig) { + this.config = config; + } + + /** + * Evaluates an ERC-191 message + */ + async evaluateERC191Message(payload: string, walletAddress: string, chainId: number, user: User): Promise { + // User is guaranteed to be authenticated when this is called + // (ensured by ensureAuthenticated() in provider) + // Trust provider - use access_token directly + + let data: MessageEvaluationResponse; + try { + data = await authenticatedFetch( + `${this.config.guardianUrl}/v1/erc191-messages/evaluate`, + { + method: 'POST', + body: { + chainID: getEip155ChainId(chainId), + payload, + }, + token: user.access_token, + } + ); + } catch (error: any) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + `Message failed to validate: ${error.message}` + ); + } + + // Handle confirmation if required + if (data.confirmationRequired && data.messageId) { + const confirmed = await this.handleMessageConfirmation( + data.messageId, + walletAddress, + 'erc191' + ); + + if (!confirmed) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Signature rejected by user' + ); + } + } + } + + /** + * Evaluates an EIP-712 message + */ + async evaluateEIP712Message(payload: TypedDataPayload, walletAddress: string, chainId: number, user: User): Promise { + // User is guaranteed to be authenticated when this is called + // (ensured by ensureAuthenticated() in provider) + // Trust provider - use access_token directly + + let data: MessageEvaluationResponse; + try { + data = await authenticatedFetch( + `${this.config.guardianUrl}/v1/eip712-messages/evaluate`, + { + method: 'POST', + body: { + chainID: getEip155ChainId(chainId), + payload, + }, + token: user.access_token, + } + ); + } catch (error: any) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + `Message failed to validate: ${error.message}` + ); + } + + // Handle confirmation if required + if (data.confirmationRequired && data.messageId) { + const confirmed = await this.handleMessageConfirmation( + data.messageId, + walletAddress, + 'eip712' + ); + + if (!confirmed) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Signature rejected by user' + ); + } + } + } + + /** + * Handles message confirmation + */ + private async handleMessageConfirmation( + messageId: string, + walletAddress: string, + messageType: 'erc191' | 'eip712' + ): Promise { + const result = await this.config.confirmationScreen.requestMessageConfirmation( + messageId, + walletAddress, + messageType + ); + return result.confirmed; + } + + /** + * Handles transaction confirmation + */ + private async handleTransactionConfirmation( + transactionId: string, + walletAddress: string, + chainId: number + ): Promise { + const result = await this.config.confirmationScreen.requestConfirmation( + transactionId, + walletAddress, + getEip155ChainId(chainId) + ); + return result.confirmed; + } + + /** + * Maps meta-transactions to Guardian API format + * Guardian-specific transformation logic (converts bigint to string for JSON) + */ + private mapMetaTransactionsForGuardian( + metaTransactions: Array<{ + target: `0x${string}`; + value: bigint; + data: `0x${string}`; + gasLimit: bigint; + delegateCall: boolean; + revertOnError: boolean; + }> + ): Array<{ + delegateCall: boolean; + revertOnError: boolean; + gasLimit: string; + target: string; + value: string; + data: string; + }> { + return metaTransactions.map((tx) => ({ + delegateCall: tx.delegateCall, + revertOnError: tx.revertOnError, + gasLimit: tx.gasLimit.toString(), + target: tx.target, + value: tx.value.toString(), + data: tx.data, + })); + } + + /** + * Validates EVM transaction with Guardian API + */ + async validateEVMTransaction({ + chainId, + nonce, + metaTransactions, + walletAddress, + isBackgroundTransaction, + user, + }: { + chainId: string; + nonce: string | bigint; + metaTransactions: Array<{ + target: `0x${string}`; + value: bigint; + data: `0x${string}`; + gasLimit: bigint; + delegateCall: boolean; + revertOnError: boolean; + }>; + walletAddress: string; + isBackgroundTransaction?: boolean; + user: User; + }): Promise { + // User is guaranteed to be authenticated when this is called + // (ensured by ensureAuthenticated() in provider) + // Trust provider - use access_token directly + + // Transform meta-transactions for Guardian API + const guardianTransactions = this.mapMetaTransactionsForGuardian(metaTransactions); + + let data: TransactionEvaluationResponse; + try { + data = await authenticatedFetch( + `${this.config.guardianUrl}/v1/transactions/evm/evaluate`, + { + method: 'POST', + body: { + chainType: 'evm', + chainId, + transactionData: { + nonce: nonce.toString(), + userAddress: walletAddress, + metaTransactions: guardianTransactions, + }, + }, + token: user.access_token, + } + ); + } catch (error: any) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + `Transaction failed to validate: ${error.message}` + ); + } + + // Handle confirmation if required + if (data.confirmationRequired && data.transactionId) { + const confirmed = await this.handleTransactionConfirmation( + data.transactionId, + walletAddress, + parseInt(chainId.split(':')[1] || chainId) + ); + + if (!confirmed) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Transaction rejected by user' + ); + } + } else if (!isBackgroundTransaction) { + // Close confirmation screen if not background transaction + this.config.confirmationScreen.closeWindow(); + } + } +} diff --git a/pkg/wallet/src/index.ts b/pkg/wallet/src/index.ts new file mode 100644 index 0000000000..fbb59ba10c --- /dev/null +++ b/pkg/wallet/src/index.ts @@ -0,0 +1,129 @@ +/** + * @imtbl/wallet - Minimal wallet package + * + * Provides EIP-1193 compatible wallet provider for Immutable zkEVM. + * Can be used standalone or with authenticated user for enhanced functionality. + */ + +import type { Auth } from '@imtbl/auth'; +import type { Provider, ChainConfig } from './provider'; + +/** + * Wallet configuration (optional) + */ +export interface WalletConfig { + /** Chain configurations - if omitted, uses default Immutable chains */ + chains?: ChainConfig[]; + /** Initial chain ID (defaults to first chain) */ + initialChainId?: number; + /** Optional auth client - login/getUser handled automatically */ + auth?: Auth; + /** Optional popup overlay options */ + popupOverlayOptions?: { + disableGenericPopupOverlay?: boolean; + disableBlockedPopupOverlay?: boolean; + }; + /** Announce provider via EIP-6963 (default: true) */ + announceProvider?: boolean; +} + +/** + * Signer interface - library agnostic + * Compatible with ethers Signer, viem WalletClient, or custom implementations + */ +export type { Signer } from './signer/signer'; + + +/** + * Connects to Immutable Passport wallet and returns an EIP-1193 compatible provider. + * + * Returns provider directly - use with any EIP-1193 compatible library. + * Signer is handled automatically internally when user is authenticated. + * + * @example + * ```typescript + * // Standalone (zero config - uses default Immutable chains) + * import { connectWallet } from '@imtbl/wallet'; + * + * const provider = await connectWallet(); + * + * // With custom chain configuration + * const provider = await connectWallet({ + * chains: [{ + * chainId: 13371, + * rpcUrl: 'https://rpc.immutable.com', + * relayerUrl: 'https://api.immutable.com/relayer-mr', + * apiUrl: 'https://api.immutable.com', + * name: 'Immutable zkEVM', + * }], + * }); + * + * // Use with viem + * import { createWalletClient, custom } from 'viem'; + * const client = createWalletClient({ + * transport: custom(provider), + * }); + * + * // Request accounts + * const accounts = await provider.request({ method: 'eth_requestAccounts' }); + * + * // With authentication (enhanced features) + * // Login/getUser handled automatically under the hood + * import { Auth } from '@imtbl/auth'; + * + * const auth = new Auth({ clientId: '...', redirectUri: '...' }); + * const provider = await connectWallet({ auth }); + * // Auth client handles login/getUser automatically when needed + * // Signer is automatically initialized internally + * ``` + */ +import { PassportEVMProvider } from './provider'; +import { announceProvider, passportProviderInfo } from './eip6963'; + +export async function connectWallet( + config?: WalletConfig +): Promise { + // Use provided chains or default Immutable chains + const chains = config?.chains || getDefaultChains(); + + const provider = new PassportEVMProvider({ + chains, + initialChainId: config?.initialChainId, + authenticatedUser: undefined, // Will be set automatically when login is triggered + auth: config?.auth, // Pass auth client for automatic login + popupOverlayOptions: config?.popupOverlayOptions, + }); + + // Announce provider via EIP-6963 if requested + if (config?.announceProvider !== false) { + announceProvider({ + info: passportProviderInfo, + provider, + }); + } + + return provider; +} + +/** + * Gets default Immutable chain configurations + */ +function getDefaultChains(): ChainConfig[] { + return [ + { + chainId: 13473, + rpcUrl: 'https://rpc.testnet.immutable.com', + relayerUrl: 'https://api.sandbox.immutable.com/relayer-mr', + apiUrl: 'https://api.sandbox.immutable.com', + name: 'Immutable zkEVM Testnet', + }, + { + chainId: 13371, + rpcUrl: 'https://rpc.immutable.com', + relayerUrl: 'https://api.immutable.com/relayer-mr', + apiUrl: 'https://api.immutable.com', + name: 'Immutable zkEVM', + }, + ]; +} + diff --git a/pkg/wallet/src/metatransaction.ts b/pkg/wallet/src/metatransaction.ts new file mode 100644 index 0000000000..bce3eabd86 --- /dev/null +++ b/pkg/wallet/src/metatransaction.ts @@ -0,0 +1,194 @@ +/** + * Meta-transaction building and signing utilities + */ + +import type { User } from '@imtbl/auth'; +import { toHex, createPublicClient, http } from 'viem'; +import { JsonRpcError, RpcErrorCode } from './errors'; +import type { RelayerClient } from './relayer'; +import type { GuardianClient } from './guardian'; +import type { Signer } from './signer/signer'; +import { getFunctionSelector } from './utils/abi'; +import { signMetaTransactions } from './sequence'; +import { getEip155ChainId } from './utils/chain'; + +/** + * Meta-transaction structure (all fields required) + */ +export interface MetaTransaction { + gasLimit: bigint; + target: `0x${string}`; + value: bigint; + data: `0x${string}`; + delegateCall: boolean; + revertOnError: boolean; +} + +/** + * Transaction request (from eth_sendTransaction) + */ +export interface TransactionRequest { + to: string; + data?: string; + value?: bigint | string; + nonce?: bigint | number; + chainId?: number; +} + +/** + * Builds a MetaTransaction from TransactionRequest with defaults + * Exported for use in ejection transactions and other cases where + * we need to convert TransactionRequest to MetaTransaction directly + */ +export function buildMetaTransaction( + request: TransactionRequest +): MetaTransaction { + return { + target: (request.to || '0x0000000000000000000000000000000000000000') as `0x${string}`, + value: typeof request.value === 'string' + ? BigInt(request.value) + : (request.value ?? BigInt(0)), + data: (request.data || '0x') as `0x${string}`, + gasLimit: BigInt(0), // Default, relayer handles gas + delegateCall: false, + revertOnError: true, + }; +} + + +/** + * Gets nonce from smart contract wallet via RPC + * Returns 0 if wallet is not deployed (BAD_DATA error) + * Encodes nonce with space (space in upper 160 bits, nonce in lower 96 bits) + */ +export async function getNonce( + rpcUrl: string, + smartContractWalletAddress: string, + nonceSpace?: bigint +): Promise { + const space = nonceSpace || BigInt(0); + + // Read nonce from wallet contract using eth_call + // Function signature: readNonce(uint256 space) returns (uint256) + const functionSelector = getFunctionSelector('readNonce(uint256)'); + + // Encode the space parameter + const spaceHex = toHex(space, { size: 32 }); + const data = functionSelector + spaceHex.slice(2); + + try { + const client = createPublicClient({ + transport: http(rpcUrl), + }); + const result = await client.call({ + to: smartContractWalletAddress as `0x${string}`, + data: data as `0x${string}`, + }); + + if (result?.data && result.data !== '0x') { + const nonce = BigInt(result.data); + // Encode nonce with space (space in upper 160 bits, nonce in lower 96 bits) + const shiftedSpace = space * (BigInt(2) ** BigInt(96)); + return nonce + shiftedSpace; + } + + // If result is 0x or empty, wallet might not be deployed + return BigInt(0); + } catch (error: any) { + // BAD_DATA error usually means wallet not deployed + if (error?.message?.includes('BAD_DATA') || error?.message?.includes('execution reverted')) { + return BigInt(0); + } + throw error; + } +} + + +/** + * Builds meta-transactions array with fee transaction + * Returns transactions directly in normalized format (no intermediate type) + * Also returns the nonce used for signing + */ +export async function buildMetaTransactions( + transactionRequest: TransactionRequest, + rpcUrl: string, + relayerClient: RelayerClient, + zkevmAddress: string, + chainId: number, + user: User, + nonceSpace?: bigint +): Promise<{ transactions: [MetaTransaction, ...MetaTransaction[]]; nonce: bigint }> { + if (!transactionRequest.to) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'eth_sendTransaction requires a "to" field' + ); + } + + // Build transaction for fee estimation (nonce doesn't matter for fees) + const txForFeeEstimation = buildMetaTransaction(transactionRequest); + + // Get nonce and fee option in parallel + const [nonce, feeOption] = await Promise.all([ + getNonce(rpcUrl, zkevmAddress, nonceSpace), + relayerClient.getFeeOption(zkevmAddress, [txForFeeEstimation], chainId, user), + ]); + + // Build final transactions with valid nonce + const metaTransactions: [MetaTransaction, ...MetaTransaction[]] = [ + buildMetaTransaction(transactionRequest), + ]; + + // Add fee transaction if fee is non-zero + const feeValue = BigInt(feeOption.tokenPrice); + if (feeValue !== BigInt(0)) { + metaTransactions.push({ + target: feeOption.recipientAddress as `0x${string}`, + value: feeValue, + data: '0x' as `0x${string}`, + gasLimit: BigInt(0), + delegateCall: false, + revertOnError: true, + }); + } + + return { transactions: metaTransactions, nonce }; +} + +/** + * Validates and signs meta-transactions in parallel + * Consolidates the common pattern used in handleSendTransaction and deployWallet + */ +export async function validateAndSignTransaction( + metaTransactions: MetaTransaction[], + nonce: bigint, + chainId: bigint, + walletAddress: string, + signer: Signer, + guardianClient: GuardianClient, + user: User, + isBackgroundTransaction: boolean = false +): Promise { + const [, signedTransactionData] = await Promise.all([ + guardianClient.validateEVMTransaction({ + chainId: getEip155ChainId(Number(chainId)), + nonce, + metaTransactions, + walletAddress, + isBackgroundTransaction, + user, + }), + signMetaTransactions( + metaTransactions, + nonce, + chainId, + walletAddress, + signer + ), + ]); + + return signedTransactionData; +} + + + diff --git a/pkg/wallet/src/provider.ts b/pkg/wallet/src/provider.ts new file mode 100644 index 0000000000..efbae860a6 --- /dev/null +++ b/pkg/wallet/src/provider.ts @@ -0,0 +1,1083 @@ +/** + * PassportEVMProvider - Minimal EIP-1193 provider for Immutable EVM chains + * + */ + +import type { User, Auth } from '@imtbl/auth'; +import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from './errors'; +import { toHex, fromHex, createPublicClient, http, isAddress } from 'viem'; +import { getFunctionSelector } from './utils/abi'; +import type { Signer } from './signer/signer'; +import { MagicTEESigner } from './signer/magic'; +import { RelayerClient } from './relayer'; +import { GuardianClient } from './guardian'; +import { ApiClient } from './api'; +import { ConfirmationScreen } from './confirmation/confirmation'; +import { hexToString } from './utils/hex'; +import { packSignatures } from './sequence'; +import { signERC191Message, signTypedData } from './signer/signing'; +import { buildMetaTransactions, validateAndSignTransaction } from './metatransaction'; +import { prepareAndSignEjectionTransaction } from './ejection'; +import type { TransactionRequest } from './metatransaction'; + +/** + * Gets passport domain based on chain configuration + */ +function getPassportDomain(chains: ChainConfig[]): string { + // If any chain uses sandbox API, use sandbox passport domain + const isSandbox = chains.some(chain => + chain.apiUrl.includes('sandbox') || chain.apiUrl.includes('testnet') + ); + return isSandbox + ? 'https://passport.sandbox.immutable.com' + : 'https://passport.immutable.com'; +} + +/** + * Simple event emitter for provider events + */ +class SimpleEventEmitter { + private listeners: Map void>> = new Map(); + + on(event: string, listener: (...args: any[]) => void): void { + if (!this.listeners.has(event)) { + this.listeners.set(event, new Set()); + } + this.listeners.get(event)!.add(listener); + } + + removeListener(event: string, listener: (...args: any[]) => void): void { + this.listeners.get(event)?.delete(listener); + } + + emit(event: string, ...args: any[]): void { + this.listeners.get(event)?.forEach(listener => listener(...args)); + } +} + +/** + * Chain configuration + */ +export interface ChainConfig { + /** Chain ID (e.g., 13371 for mainnet, 13473 for testnet) */ + chainId: number; + /** RPC URL for the chain */ + rpcUrl: string; + /** Relayer URL for transaction submission */ + relayerUrl: string; + /** API URL for Passport APIs (guardian, user registration) */ + apiUrl: string; + /** Chain name (e.g., 'Immutable zkEVM') */ + name: string; +} + +/** + * Provider configuration + */ +export interface ProviderConfig { + /** Chain configurations - at least one required */ + chains: ChainConfig[]; + /** Initial chain ID (defaults to first chain in chains array) */ + initialChainId?: number; + /** Optional authenticated user */ + authenticatedUser?: User; + /** Optional auth client for automatic login/getUser */ + auth?: Auth; + /** Optional popup overlay options */ + popupOverlayOptions?: { + disableGenericPopupOverlay?: boolean; + disableBlockedPopupOverlay?: boolean; + }; +} + +/** + * EIP-1193 compatible provider interface + */ +export interface Provider { + /** + * Send a JSON-RPC request + * @see https://eips.ethereum.org/EIPS/eip-1193 + */ + request(args: RequestArguments): Promise; + + /** + * Subscribe to provider events + */ + on(event: string, listener: (...args: any[]) => void): void; + + /** + * Unsubscribe from provider events + */ + removeListener(event: string, listener: (...args: any[]) => void): void; + + /** + * Indicates this is a Passport provider + */ + isPassport: boolean; +} + +/** + * JSON-RPC request arguments + */ +export interface RequestArguments { + method: string; + params?: Array; +} + +/** + * Provider event types + */ +export enum ProviderEvent { + ACCOUNTS_CHANGED = 'accountsChanged', +} + +/** + * PassportEVMProvider - EIP-1193 compatible provider + */ +export class PassportEVMProvider implements Provider { + public readonly isPassport: boolean = true; + + private chains: Map; + private currentChainId: number; + private currentRpcUrl: string; + private eventEmitter: SimpleEventEmitter; + private authenticatedUser?: User; + private auth?: Auth; + private signer?: Signer; + private walletAddress?: string; + private passportDomain: string; + // Clients are always initialized in constructor via initializeClients() + private relayerClient!: RelayerClient; + private guardianClient!: GuardianClient; + private apiClient!: ApiClient; + private confirmationScreen: ConfirmationScreen; + // Cached HTTP transport for RPC calls (recreated when chain switches) + private rpcTransport = http(''); + + constructor(config: ProviderConfig) { + if (!config.chains || config.chains.length === 0) { + throw new Error('At least one chain configuration is required'); + } + + // Build chain map + this.chains = new Map(); + for (const chain of config.chains) { + this.chains.set(chain.chainId, chain); + } + + // Set initial chain + this.currentChainId = config.initialChainId || config.chains[0].chainId; + const chainConfig = this.chains.get(this.currentChainId); + if (!chainConfig) { + throw new Error(`Chain ${this.currentChainId} not configured`); + } + + this.currentRpcUrl = chainConfig.rpcUrl; + this.authenticatedUser = config.authenticatedUser; + this.auth = config.auth; + this.eventEmitter = new SimpleEventEmitter(); + + // Initialize RPC transport + this.rpcTransport = http(this.currentRpcUrl); + + // Determine passport domain from chain config + this.passportDomain = getPassportDomain(config.chains); + + // Initialize confirmation screen + this.confirmationScreen = new ConfirmationScreen({ + passportDomain: this.passportDomain, + popupOverlayOptions: config.popupOverlayOptions, + }); + + // Initialize clients eagerly (they can exist without authenticated user) + // Clients are stateless - user is passed as parameter to methods + this.initializeClients(); + + // If auth client provided, set it up (will try to get existing user) + if (this.auth) { + this.setAuth(this.auth); + } + + // If authenticated user provided, set it up + if (this.authenticatedUser) { + this.setAuthenticatedUser(this.authenticatedUser); + } + } + + /** + * Initializes or updates API clients + * Clients can be created without authenticated user - they'll fail on requests until user is set + */ + private initializeClients(): void { + const chainConfig = this.chains.get(this.currentChainId)!; + + this.relayerClient = new RelayerClient({ + relayerUrl: chainConfig.relayerUrl, + }); + + this.guardianClient = new GuardianClient({ + guardianUrl: chainConfig.apiUrl, + confirmationScreen: this.confirmationScreen, + }); + + this.apiClient = new ApiClient({ + apiUrl: chainConfig.apiUrl, + }); + } + + /** + * Sets the authenticated user (internal use only) + * Automatically initializes MagicTEESigner when user is authenticated + * @internal + */ + private setAuthenticatedUser(user: User): void { + this.authenticatedUser = user; + this.initializeClients(); + this.initializeSigner(); + } + + /** + * Initializes MagicTEESigner automatically when user is authenticated + * Signer is handled internally - clients should not need to provide their own + */ + private initializeSigner(): void { + if (!this.authenticatedUser) { + return; + } + + // Magic TEE config - hardcoded based on passport domain + const isSandbox = this.passportDomain.includes('sandbox'); + const magicTeeBasePath = isSandbox + ? 'https://api.sandbox.immutable.com/magic-tee' + : 'https://api.immutable.com/magic-tee'; + + // Magic config values - these should be consistent across environments + // TODO: These might need to come from config or environment + const magicPublishableApiKey = process.env.MAGIC_PUBLISHABLE_API_KEY || ''; + const magicProviderId = process.env.MAGIC_PROVIDER_ID || 'immutable'; + + // Only initialize if we have the required config + if (magicPublishableApiKey && magicProviderId) { + this.signer = new MagicTEESigner({ + magicTeeBasePath, + magicPublishableApiKey, + magicProviderId, + authenticatedUser: this.authenticatedUser, + }); + } + } + + /** + * Sets the auth client (internal use only) + * For automatic login/getUser - handled internally by provider + * @internal + */ + private setAuth(auth: Auth): void { + this.auth = auth; + // Try to get existing user + auth.getUser().then((user: User | null) => { + if (user) { + this.setAuthenticatedUser(user); + } + }).catch(() => { + // No user yet - will be handled when needed + }); + } + + /** + * Sets the signer (internal use only) + * Signer is automatically initialized when user is authenticated. + * This method is kept for backward compatibility but should not be used by clients. + * + * @internal + */ + setSigner(signer: Signer): void { + this.signer = signer; + } + + /** + * Gets the current chain ID + */ + async getChainId(): Promise { + return this.currentChainId; + } + + /** + * Switches to a different chain + */ + async switchChain(chainId: number): Promise { + if (!this.chains.has(chainId)) { + throw new JsonRpcError( + ProviderErrorCode.UNSUPPORTED_METHOD, + `Chain ${chainId} not supported` + ); + } + + this.currentChainId = chainId; + const chainConfig = this.chains.get(chainId)!; + + this.currentRpcUrl = chainConfig.rpcUrl; + + // Recreate RPC transport for new chain + this.rpcTransport = http(this.currentRpcUrl); + + // Reinitialize clients with new chain config (always, regardless of user) + this.initializeClients(); + + // Emit chainChanged event + this.eventEmitter.emit('chainChanged', toHex(chainId)); + } + + /** + * Adds a new chain configuration + */ + addChain(chainConfig: ChainConfig): void { + this.chains.set(chainConfig.chainId, chainConfig); + } + + /** + * Gets the wallet address from authenticated user + */ + private async getWalletAddress(): Promise { + if (this.walletAddress) { + return this.walletAddress; + } + + if (!this.authenticatedUser) { + return undefined; + } + + // Try to extract from user profile (if already registered) + // The profile might contain zkEVM address in custom claims + // For now, return undefined - will be set during registration + return undefined; + } + + /** + * Ensures wallet address exists, throws if not + */ + private async ensureWalletAddress(): Promise { + const address = await this.getWalletAddress(); + if (!address) { + throw new JsonRpcError( + ProviderErrorCode.UNAUTHORIZED, + 'Unauthorised - call eth_requestAccounts first' + ); + } + return address; + } + + /** + * Ensures signer is set + * Automatically initializes MagicTEESigner if user is authenticated but signer not yet created + */ + private ensureSigner(): Signer { + // If signer not set but user is authenticated, try to initialize it + if (!this.signer && this.authenticatedUser) { + this.initializeSigner(); + } + + if (!this.signer) { + throw new JsonRpcError( + ProviderErrorCode.UNAUTHORIZED, + 'Signer not available. User must be authenticated for signing operations.' + ); + } + return this.signer; + } + + /** + * Ensures user is authenticated, automatically triggers login if auth client is provided + */ + private async ensureAuthenticated(): Promise { + // If already authenticated, return + if (this.authenticatedUser) { + return; + } + + // If auth client provided, automatically trigger login + if (this.auth) { + const user = await this.auth.loginPopup(); + if (user) { + this.setAuthenticatedUser(user); + return; + } + } + + // If still not authenticated, throw error + throw new JsonRpcError( + ProviderErrorCode.UNAUTHORIZED, + 'User not authenticated. Please provide an auth client or login first.' + ); + } + + /** + * Ensures everything is ready for signing operations + * Automatically triggers login if auth client is provided + */ + private async ensureSigningReady(): Promise<{ + address: string; + signer: Signer; + relayerClient: RelayerClient; + guardianClient: GuardianClient; + }> { + // Ensure authenticated (will auto-login if auth client provided) + await this.ensureAuthenticated(); + + // Ensure wallet address (will register if needed) + const address = await this.ensureWalletAddress(); + + // Ensure signer is set + const signer = this.ensureSigner(); + + return { address, signer, relayerClient: this.relayerClient, guardianClient: this.guardianClient }; + } + + /** + * Handles eth_requestAccounts + * Automatically triggers login if auth client is provided and user not authenticated + */ + private async handleRequestAccounts(): Promise { + // Check if we already have a wallet address + const address = await this.getWalletAddress(); + if (address) { + this.eventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [address]); + return [address]; + } + + // Ensure authenticated (will auto-login if auth client provided) + await this.ensureAuthenticated(); + + // Ensure signer is set + const signer = this.ensureSigner(); + + const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport'; + + // Get signer address and sign message + const [ethereumAddress, ethereumSignature] = await Promise.all([ + signer.getAddress(), + signer.signMessage(MESSAGE_TO_SIGN), + ]); + + // Get chain name from API + const chainName = await this.apiClient.getChainName(this.currentChainId); + + // Register user + const counterfactualAddress = await this.apiClient.createCounterfactualAddress( + chainName, + ethereumAddress, + ethereumSignature, + this.authenticatedUser! + ); + + this.walletAddress = counterfactualAddress; + this.eventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [counterfactualAddress]); + + return [counterfactualAddress]; + } + + /** + * Handles eth_sendTransaction + */ + private async handleSendTransaction(params: any[]): Promise { + const address = await this.ensureWalletAddress(); + const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + + const transactionRequest = params[0]; + if (!transactionRequest?.to) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'Transaction must include to field' + ); + } + + // Validate address format + if (!isAddress(transactionRequest.to)) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + `Invalid address: ${transactionRequest.to}` + ); + } + + // Build meta-transactions (includes fee transaction) + const { transactions: metaTransactions, nonce } = await buildMetaTransactions( + { + to: transactionRequest.to, + data: transactionRequest.data, + value: transactionRequest.value ? BigInt(transactionRequest.value) : undefined, + }, + this.currentRpcUrl, + relayerClient, + address, + this.currentChainId, + this.authenticatedUser! + ); + + const chainId = BigInt(this.currentChainId); + + // Validate and sign in parallel + const signedTransactionData = await validateAndSignTransaction( + metaTransactions, + nonce, + chainId, + address, + signer, + guardianClient, + this.authenticatedUser!, + false + ); + + // Send to relayer + const relayerId = await relayerClient.ethSendTransaction( + address, // to is the wallet address + signedTransactionData, + this.currentChainId, + this.authenticatedUser! + ); + + // Poll for transaction hash + return this.pollTransaction(relayerId, relayerClient); + } + + /** + * Polls relayer for transaction hash + */ + private async pollTransaction(relayerId: string, relayerClient: RelayerClient): Promise { + if (!this.authenticatedUser) { + throw new JsonRpcError( + ProviderErrorCode.UNAUTHORIZED, + 'User not authenticated' + ); + } + + const maxAttempts = 30; + const delayMs = 1000; + + for (let i = 0; i < maxAttempts; i++) { + const tx = await relayerClient.imGetTransactionByHash(relayerId, this.authenticatedUser); + + if (tx.status === 'SUCCESSFUL' || tx.status === 'SUBMITTED') { + return tx.hash; + } + + if (tx.status === 'REVERTED' || tx.status === 'FAILED') { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + tx.statusMessage || 'Transaction failed' + ); + } + + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + + throw new JsonRpcError( + RpcErrorCode.RPC_SERVER_ERROR, + 'Transaction polling timeout' + ); + } + + /** + * Handles personal_sign + */ + private async handlePersonalSign(params: any[]): Promise { + const { address, signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + + const message: string = params[0]; + const fromAddress: string = params[1]; + + if (!fromAddress || !message) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'personal_sign requires an address and a message' + ); + } + + // Validate address format + if (!isAddress(fromAddress)) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + `Invalid address: ${fromAddress}` + ); + } + + if (fromAddress.toLowerCase() !== address.toLowerCase()) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'personal_sign requires the signer to be the from address' + ); + } + + + // Convert hex to string if needed + const payload = hexToString(message); + const chainId = BigInt(this.currentChainId); + + // Evaluate with guardian (passes wallet address for confirmation) + await guardianClient.evaluateERC191Message(payload, address, this.currentChainId, this.authenticatedUser!); + + // Sign with EOA and get relayer signature in parallel + const [eoaSignature, relayerSignature] = await Promise.all([ + signERC191Message(chainId, payload, signer, address), + relayerClient.imSign(address, payload, this.currentChainId, this.authenticatedUser!), + ]); + + const eoaAddress = await signer.getAddress(); + + // Pack signatures + return packSignatures(eoaSignature, eoaAddress, relayerSignature); + } + + /** + * Deploys the smart contract wallet by sending a zero-value transaction + */ + private async deployWallet(): Promise { + const address = await this.ensureWalletAddress(); + const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + + // Build meta-transactions for deployment (zero-value transaction to self) + const { transactions: metaTransactions, nonce } = await buildMetaTransactions( + { + to: address, + data: '0x', + value: BigInt(0), + }, + this.currentRpcUrl, + relayerClient, + address, + this.currentChainId, + this.authenticatedUser! + ); + + const chainId = BigInt(this.currentChainId); + + // Validate and sign in parallel + const signedTransactionData = await validateAndSignTransaction( + metaTransactions, + nonce, + chainId, + address, + signer, + guardianClient, + this.authenticatedUser!, + false + ); + + // Send to relayer + const relayerId = await relayerClient.ethSendTransaction( + address, + signedTransactionData, + this.currentChainId, + this.authenticatedUser! + ); + + // Wait for deployment to complete + await this.pollTransaction(relayerId, relayerClient); + } + + /** + * Handles eth_signTypedData_v4 + */ + private async handleSignTypedDataV4(params: any[]): Promise { + const { address, signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + + const fromAddress: string = params[0]; + const typedDataParam: string | object = params[1]; + + if (!fromAddress || !typedDataParam) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'eth_signTypedData_v4 requires an address and typed data' + ); + } + + // Validate address format + if (!isAddress(fromAddress)) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + `Invalid address: ${fromAddress}` + ); + } + + // Parse typed data + const typedData: any = typeof typedDataParam === 'string' + ? JSON.parse(typedDataParam) + : typedDataParam; + + // Validate typed data structure + if (!typedData.types || !typedData.domain || !typedData.primaryType || !typedData.message) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'Invalid typed data: missing required fields' + ); + } + + // Validate chainId matches current chain + if (typedData.domain.chainId) { + const providedChainId = typeof typedData.domain.chainId === 'string' + ? (typedData.domain.chainId.startsWith('0x') + ? parseInt(typedData.domain.chainId, 16) + : parseInt(typedData.domain.chainId, 10)) + : typedData.domain.chainId; + + if (BigInt(providedChainId) !== BigInt(this.currentChainId)) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + `Invalid chainId, expected ${this.currentChainId}` + ); + } + } + + const chainId = BigInt(this.currentChainId); + + // Evaluate with guardian (passes wallet address for confirmation) + await guardianClient.evaluateEIP712Message(typedData, address, this.currentChainId, this.authenticatedUser!); + + // Get relayer signature + const relayerSignature = await relayerClient.imSignTypedData(address, typedData, this.currentChainId, this.authenticatedUser!); + + // If signer has signTypedData method, use it (ethers/viem signers) + if (signer.signTypedData) { + const eoaSignature = await signer.signTypedData( + typedData.domain, + typedData.types, + typedData.message + ); + const eoaAddress = await signer.getAddress(); + return packSignatures(eoaSignature, eoaAddress, relayerSignature); + } + + // Otherwise, use our implementation + return signTypedData(typedData, relayerSignature, chainId, address, signer); + } + + /** + * Handles wallet_switchEthereumChain + */ + private async handleSwitchChain(params: any[]): Promise { + const chainIdHex = params[0]?.chainId; + if (!chainIdHex) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'chainId is required' + ); + } + + const chainId = typeof chainIdHex === 'string' + ? Number(fromHex(chainIdHex as `0x${string}`, 'number')) + : chainIdHex; + + await this.switchChain(chainId); + return null; + } + + /** + * Handles wallet_addEthereumChain + */ + private async handleAddChain(params: any[]): Promise { + const chainParams = params[0]; + if (!chainParams?.chainId || !chainParams?.rpcUrls?.[0]) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'chainId and rpcUrls are required' + ); + } + + const chainId = typeof chainParams.chainId === 'string' + ? Number(fromHex(chainParams.chainId as `0x${string}`, 'number')) + : chainParams.chainId; + + // Extract API URLs from chain params or use defaults + const apiUrl = chainParams.apiUrl || this.chains.get(this.currentChainId)?.apiUrl || ''; + const relayerUrl = chainParams.relayerUrl || this.chains.get(this.currentChainId)?.relayerUrl || ''; + + this.addChain({ + chainId, + rpcUrl: chainParams.rpcUrls[0], + relayerUrl, + apiUrl, + name: chainParams.chainName || `Chain ${chainId}`, + }); + + return null; + } + + /** + * Handles im_signEjectionTransaction + */ + private async handleSignEjectionTransaction(params: any[]): Promise<{ + to: string; + data: string; + chainId: string; + }> { + const address = await this.ensureWalletAddress(); + const { signer } = await this.ensureSigningReady(); + + if (!params || params.length !== 1) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a singular param (transaction)' + ); + } + + const transactionRequest: TransactionRequest = params[0]; + + return prepareAndSignEjectionTransaction({ + transactionRequest, + ethSigner: signer, + zkEvmAddress: address, + chainId: this.currentChainId, + }); + } + + /** + * Handles im_addSessionActivity + */ + private async handleAddSessionActivity(params: any[]): Promise { + const address = await this.ensureWalletAddress(); + const [clientId] = params || []; + + if (!clientId) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_addSessionActivity requires a clientId' + ); + } + + // Session activity is handled asynchronously and doesn't block + // This is a fire-and-forget operation for analytics + this.callSessionActivity(address, clientId).catch(() => { + // Silently fail - session activity is non-critical + }); + + return null; + } + + /** + * Calls session activity API (background operation) + */ + private async callSessionActivity(walletAddress: string, clientId: string): Promise { + if (!this.authenticatedUser?.access_token) { + return; // Skip if not authenticated + } + + // Determine API URL based on chain configuration + const chainConfig = this.chains.get(this.currentChainId); + if (!chainConfig) { + return; + } + + const apiUrl = chainConfig.apiUrl; + const isSandbox = apiUrl.includes('sandbox') || apiUrl.includes('testnet'); + + // Session activity always uses production API + const sessionActivityUrl = 'https://api.immutable.com/v1/sdk/session-activity/check'; + + try { + const response = await fetch( + `${sessionActivityUrl}?clientId=${encodeURIComponent(clientId)}&wallet=${encodeURIComponent(walletAddress)}&checkCount=0&sendCount=0`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${this.authenticatedUser.access_token}`, + }, + } + ); + + if (!response.ok) { + if (response.status === 404) { + return; // No session activity required + } + throw new Error(`Session activity error: ${response.status}`); + } + + const data = await response.json(); + + // If contract address and function name are provided, send a background transaction + if (data.contractAddress && data.functionName) { + // Send background transaction in nonce space 1 + await this.sendBackgroundTransaction( + walletAddress, + data.contractAddress, + data.functionName, + data.delay + ); + } + } catch (error) { + // Silently fail - session activity is non-critical + } + } + + /** + * Sends a background transaction for session activity + */ + private async sendBackgroundTransaction( + walletAddress: string, + contractAddress: string, + functionName: string, + delay?: number + ): Promise { + try { + const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + + // Encode function call (simple function with no parameters) + const functionSelector = getFunctionSelector(`${functionName}()`); + + // Build meta-transactions with nonce space 1 (background) + const nonceSpace = BigInt(1); + const { transactions: metaTransactions, nonce } = await buildMetaTransactions( + { + to: contractAddress, + data: functionSelector, + }, + this.currentRpcUrl, + relayerClient, + walletAddress, + this.currentChainId, + this.authenticatedUser!, + nonceSpace + ); + + const chainId = BigInt(this.currentChainId); + + // Validate and sign in parallel + const signedTransactionData = await validateAndSignTransaction( + metaTransactions, + nonce, + chainId, + walletAddress, + signer, + guardianClient, + this.authenticatedUser!, + true // isBackgroundTransaction + ); + + // Send to relayer + await relayerClient.ethSendTransaction( + walletAddress, + signedTransactionData, + this.currentChainId, + this.authenticatedUser! + ); + + // Wait for delay if specified + if (delay && delay > 0) { + await new Promise(resolve => setTimeout(resolve, delay * 1000)); + } + } catch (error) { + // Silently fail - background transaction is non-critical + } + } + + /** + * Performs the actual request handling + */ + private async performRequest(request: RequestArguments): Promise { + switch (request.method) { + case 'eth_requestAccounts': { + return this.handleRequestAccounts(); + } + case 'eth_accounts': { + const address = await this.getWalletAddress(); + return address ? [address] : []; + } + case 'eth_chainId': { + return toHex(this.currentChainId); + } + case 'eth_sendTransaction': { + return this.handleSendTransaction(request.params || []); + } + case 'personal_sign': { + return this.handlePersonalSign(request.params || []); + } + case 'eth_signTypedData': + case 'eth_signTypedData_v4': { + return this.handleSignTypedDataV4(request.params || []); + } + case 'wallet_switchEthereumChain': { + return this.handleSwitchChain(request.params || []); + } + case 'wallet_addEthereumChain': { + return this.handleAddChain(request.params || []); + } + // Pass through read-only methods to RPC + case 'eth_getBalance': + case 'eth_getCode': + case 'eth_getTransactionCount': + case 'eth_getStorageAt': + case 'eth_call': + case 'eth_estimateGas': + case 'eth_gasPrice': + case 'eth_blockNumber': + case 'eth_getBlockByHash': + case 'eth_getBlockByNumber': + case 'eth_getTransactionByHash': + case 'eth_getTransactionReceipt': { + const client = createPublicClient({ + transport: this.rpcTransport, + }); + switch (request.method) { + case 'eth_gasPrice': + return client.getGasPrice(); + case 'eth_blockNumber': + return client.getBlockNumber(); + case 'eth_getBlockByHash': + return client.getBlock({ blockHash: (request.params?.[0] as `0x${string}`) || undefined }); + case 'eth_getBlockByNumber': + return client.getBlock({ blockNumber: request.params?.[0] as any }); + case 'eth_getTransactionByHash': + return client.getTransaction({ hash: (request.params?.[0] as `0x${string}`) || undefined }); + case 'eth_getTransactionReceipt': + return client.getTransactionReceipt({ hash: (request.params?.[0] as `0x${string}`) || undefined }); + default: + throw new JsonRpcError( + ProviderErrorCode.UNSUPPORTED_METHOD, + `Method ${request.method} not supported` + ); + } + } + case 'im_signEjectionTransaction': { + return this.handleSignEjectionTransaction(request.params || []); + } + case 'im_addSessionActivity': { + return this.handleAddSessionActivity(request.params || []); + } + default: { + throw new JsonRpcError( + ProviderErrorCode.UNSUPPORTED_METHOD, + `Method ${request.method} not supported` + ); + } + } + } + + /** + * EIP-1193 request method + */ + public async request(request: RequestArguments): Promise { + try { + return await this.performRequest(request); + } catch (error) { + if (error instanceof JsonRpcError) { + throw error; + } + if (error instanceof Error) { + throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, error.message); + } + throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, 'Internal error'); + } + } + + /** + * EIP-1193 event subscription + */ + public on(event: string, listener: (...args: any[]) => void): void { + this.eventEmitter.on(event, listener); + } + + /** + * EIP-1193 event unsubscription + */ + public removeListener(event: string, listener: (...args: any[]) => void): void { + this.eventEmitter.removeListener(event, listener); + } +} diff --git a/pkg/wallet/src/relayer.ts b/pkg/wallet/src/relayer.ts new file mode 100644 index 0000000000..0e9551f356 --- /dev/null +++ b/pkg/wallet/src/relayer.ts @@ -0,0 +1,163 @@ +/** + * Minimal relayer client for Immutable relayer API + */ + +import type { User } from '@imtbl/auth'; +import { getEip155ChainId } from './utils/chain'; +import { jsonRpcRequest } from './utils/http-client'; +import { encodeAbiParameters } from 'viem'; +import type { MetaTransaction } from './metatransaction'; + +export interface TypedDataPayload { + types: { + EIP712Domain: Array<{ name: string; type: string }>; + [key: string]: Array<{ name: string; type: string }>; + }; + domain: { + name?: string; + version?: string; + chainId?: number | string; + verifyingContract?: string; + salt?: string; + }; + primaryType: string; + message: Record; +} + +export interface RelayerClientConfig { + relayerUrl: string; +} + +/** + * Fee option from relayer + */ +export interface FeeOption { + tokenPrice: string; + tokenSymbol: string; + tokenDecimals: number; + tokenAddress: string; + recipientAddress: string; +} + +/** + * Minimal relayer client + */ +export class RelayerClient { + private config: RelayerClientConfig; + + constructor(config: RelayerClientConfig) { + this.config = config; + } + + /** + * Makes a request to the relayer API + */ + private async request(method: string, params: any[], user: User): Promise { + // User is guaranteed to be authenticated when this is called + // (ensured by ensureAuthenticated() in provider) + // Keep defensive check here as final safety net + if (!user?.access_token) { + throw new Error('User not authenticated'); + } + + return jsonRpcRequest( + `${this.config.relayerUrl}/v1/transactions`, + method, + params, + user.access_token + ); + } + + /** + * Sends a transaction via relayer + */ + async ethSendTransaction(to: string, data: string, chainId: number, user: User): Promise { + return this.request('eth_sendTransaction', [{ + to, + data, + chainId: getEip155ChainId(chainId), + }], user); + } + + /** + * Gets transaction by hash + */ + async imGetTransactionByHash(hash: string, user: User): Promise { + return this.request('im_getTransactionByHash', [hash], user); + } + + /** + * Signs a message via relayer + */ + async imSign(address: string, message: string, chainId: number, user: User): Promise { + return this.request('im_sign', [{ + chainId: getEip155ChainId(chainId), + address, + message, + }], user); + } + + /** + * Signs typed data via relayer + */ + async imSignTypedData(address: string, payload: TypedDataPayload, chainId: number, user: User): Promise { + return this.request('im_signTypedData', [{ + chainId: getEip155ChainId(chainId), + address, + eip712Payload: payload, + }], user); + } + + /** + * Gets fee options for a transaction + */ + async imGetFeeOptions(userAddress: string, data: string, chainId: number, user: User): Promise { + return this.request('im_getFeeOptions', [{ + userAddress, + data, + chainId: getEip155ChainId(chainId), + }], user); + } + + /** + * Gets fee option from relayer (prefers IMX) + * Helper that selects IMX fee option from transactions + */ + async getFeeOption( + walletAddress: string, + transactions: MetaTransaction[], + chainId: number, + user: User + ): Promise { + const META_TRANSACTIONS_TYPE = `tuple( + bool delegateCall, + bool revertOnError, + uint256 gasLimit, + address target, + uint256 value, + bytes data + )[]`; + + const encodedTransactions = encodeAbiParameters( + [{ type: META_TRANSACTIONS_TYPE }], + [transactions] + ); + + const feeOptions = await this.imGetFeeOptions(walletAddress, encodedTransactions, chainId, user); + + if (!feeOptions || !Array.isArray(feeOptions)) { + throw new Error('Invalid fee options received from relayer'); + } + + const imxFeeOption = feeOptions.find( + (feeOption) => feeOption.tokenSymbol === 'IMX' + ); + + if (!imxFeeOption) { + throw new Error('Failed to retrieve fees for IMX token'); + } + + return imxFeeOption; + } +} + diff --git a/pkg/wallet/src/sequence.ts b/pkg/wallet/src/sequence.ts new file mode 100644 index 0000000000..c94080bc92 --- /dev/null +++ b/pkg/wallet/src/sequence.ts @@ -0,0 +1,305 @@ +/** + * Sequence signature encoding/decoding + * Minimal implementation replacing @0xsequence/core dependency + * + * Sequence signature format: + * - version (1 byte): 0x00 or 0x01 + * - threshold (1 byte): number of signatures required + * - signers: array of signer data + * - isDynamic (1 bit): whether signer is dynamic + * - unrecovered (1 bit): whether signature is unrecovered + * - weight (6 bits): weight of signer + * - signature (65 bytes): actual signature + * - address (20 bytes, optional): signer address + */ + +import { isAddress, keccak256, encodeAbiParameters, fromHex } from 'viem'; +import { cleanSignature, cleanAddress, removeHexPrefix } from './utils/hex'; +import { JsonRpcError, RpcErrorCode } from './errors'; +import type { Signer } from './signer/signer'; +import { encodeMessageSubDigest } from './signer/signing'; +import { getFunctionSelector } from './utils/abi'; +import type { MetaTransaction } from './metatransaction'; + +/** + * Sequence signature constants + */ +export const SEQUENCE_VERSION = 0; +export const SIGNATURE_WEIGHT = 1; +export const TRANSACTION_SIGNATURE_THRESHOLD = 1; +export const PACKED_SIGNATURE_THRESHOLD = 2; +export const ETH_SIGN_FLAG = '02'; + +/** + * Signer data structure + */ +interface SignerData { + isDynamic?: boolean; + unrecovered?: boolean; + weight: number; + signature: string; + address?: string; +} + +/** + * Signature encoding options + */ +interface EncodeSignatureOptions { + version: number; + threshold: number; + signers: SignerData[]; +} + +/** + * Decoded signature structure + */ +interface DecodedSignature { + version: number; + threshold: number; + signers: SignerData[]; +} + +/** + * Encodes a signature in Sequence format + */ +export function encodeSignature(options: EncodeSignatureOptions): string { + const { version, threshold, signers } = options; + + // Encode header: version (1 byte) + threshold (1 byte) + reserved (1 byte) + signerCount (1 byte) + let encoded = version.toString(16).padStart(2, '0'); + encoded += threshold.toString(16).padStart(2, '0'); + encoded += '00'; // Reserved byte (always 0) + encoded += signers.length.toString(16).padStart(2, '0'); + + // Encode each signer + for (const signer of signers) { + // Pack flags and weight into 1 byte: + // bit 0: isDynamic (0 or 1) + // bit 1: unrecovered (0 or 1) + // bits 2-7: weight (0-63) + const isDynamic = signer.isDynamic ? 1 : 0; + const unrecovered = signer.unrecovered !== false ? 1 : 0; // Default to true + const weight = signer.weight & 0x3f; // Mask to 6 bits + + const flagsByte = (isDynamic | (unrecovered << 1) | (weight << 2)).toString(16).padStart(2, '0'); + encoded += flagsByte; + + // Signature (65 bytes = 130 hex chars) + const sig = cleanSignature(signer.signature, 130); + encoded += sig; + + // Address (20 bytes = 40 hex chars) - only if provided + if (signer.address) { + const addr = cleanAddress(signer.address); + if (addr.length !== 40) { + throw new Error(`Invalid address length: expected 40 hex chars, got ${addr.length}`); + } + encoded += addr; + } + } + + return `0x${encoded}`; +} + +/** + * Decodes a Sequence signature + */ +export function decodeSignature(signature: string): DecodedSignature { + const hex = removeHexPrefix(signature); + + if (hex.length < 8) { + throw new Error('Invalid signature: too short'); + } + + // Read version (1 byte) + const version = parseInt(hex.slice(0, 2), 16); + + // Read threshold (1 byte) + const threshold = parseInt(hex.slice(2, 4), 16); + + // Skip reserved byte (offset 4-6) + + // Read signers count (1 byte) + const signersCount = parseInt(hex.slice(6, 8), 16); + + const signers: SignerData[] = []; + let offset = 8; + + for (let i = 0; i < signersCount; i++) { + if (offset + 2 > hex.length) { + throw new Error('Invalid signature: incomplete signer data'); + } + + // Read flags byte + const flagsByte = parseInt(hex.slice(offset, offset + 2), 16); + offset += 2; + + const isDynamic = (flagsByte & 0x01) !== 0; + const unrecovered = (flagsByte & 0x02) !== 0; + const weight = (flagsByte >> 2) & 0x3f; + + // Read signature (65 bytes = 130 hex chars) + if (offset + 130 > hex.length) { + throw new Error('Invalid signature: incomplete signature data'); + } + const sig = `0x${hex.slice(offset, offset + 130)}`; + offset += 130; + + // Read address if present (20 bytes = 40 hex chars) + // For relayer signatures, address might not be present + let address: string | undefined; + if (offset + 40 <= hex.length) { + address = `0x${hex.slice(offset, offset + 40)}`; + offset += 40; + } + + signers.push({ + isDynamic, + unrecovered, + weight, + signature: sig, + address, + }); + } + + return { + version, + threshold, + signers, + }; +} + +/** + * Packs two signatures together using Sequence encoding format + * Decodes relayer signature, adds EOA signature, re-encodes with threshold 2 + */ +export function packSignatures( + eoaSignature: string, + eoaAddress: string, + relayerSignature: string +): string { + // Validate address format + if (!isAddress(eoaAddress)) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + `Invalid address: ${eoaAddress}` + ); + } + + // Decode relayer signature (add 0x prefix if missing, and version/threshold prefix) + const relayerSigWithPrefix = relayerSignature.startsWith('0x') + ? relayerSignature + : `0x0000${relayerSignature}`; // Add version/threshold prefix for decoding + + const decoded = decodeSignature(relayerSigWithPrefix); + const relayerSigners = decoded.signers; + + // Add EOA signature flag + const signedDigest = `${eoaSignature}${ETH_SIGN_FLAG}`; + + // Combine signers: relayer signers + EOA signer + const combinedSigners = [ + ...relayerSigners, + { + isDynamic: false, + unrecovered: true, + weight: SIGNATURE_WEIGHT, + signature: signedDigest, + address: eoaAddress, + }, + ]; + + // Sort signers by address (if address present) + const sortedSigners = combinedSigners.sort((a, b) => { + if (!a.address || !b.address) return 0; + const diff = BigInt(a.address) - BigInt(b.address); + return diff === 0n ? 0 : diff < 0n ? -1 : 1; + }); + + // Re-encode with threshold 2 + return encodeSignature({ + version: SEQUENCE_VERSION, + threshold: PACKED_SIGNATURE_THRESHOLD, + signers: sortedSigners, + }); +} + +/** + * Signs meta-transactions using Sequence signature encoding + * This is Sequence-specific logic for signing wallet transactions + */ +export async function signMetaTransactions( + metaTransactions: MetaTransaction[], + nonce: bigint, + chainId: bigint, + walletAddress: string, + signer: Signer +): Promise { + // Get digest of transactions and nonce + const META_TRANSACTIONS_TYPE = `tuple( + bool delegateCall, + bool revertOnError, + uint256 gasLimit, + address target, + uint256 value, + bytes data + )[]`; + + const packMetaTransactionsNonceData = encodeAbiParameters( + [ + { type: 'uint256' }, + { type: META_TRANSACTIONS_TYPE }, + ], + [nonce, metaTransactions] as readonly [bigint, readonly MetaTransaction[]] + ); + + const digest = keccak256(packMetaTransactionsNonceData); + + // Create sub-digest with chain ID and wallet address + const completePayload = encodeMessageSubDigest(chainId, walletAddress, digest); + const hash = keccak256(`0x${Buffer.from(completePayload, 'utf8').toString('hex')}` as `0x${string}`); + + // Sign the hash + const hashBytes = fromHex(hash, 'bytes'); + const ethsigNoType = await signer.signMessage(hashBytes); + const signedDigest = `${ethsigNoType}${ETH_SIGN_FLAG}`; + + // Encode signature using Sequence encoder + const encodedSignature = encodeSignature({ + version: SEQUENCE_VERSION, + threshold: TRANSACTION_SIGNATURE_THRESHOLD, + signers: [ + { + isDynamic: false, + unrecovered: true, + weight: SIGNATURE_WEIGHT, + signature: signedDigest, + }, + ], + }); + + // Encode execute function call + // Function: execute(tuple[] transactions, uint256 nonce, bytes signature) + const executeSelector = getFunctionSelector('execute((bool,bool,uint256,address,uint256,bytes)[],uint256,bytes)'); + + // Encode parameters + const encodedParams = encodeAbiParameters( + [ + { type: 'tuple[]', components: [ + { name: 'delegateCall', type: 'bool' }, + { name: 'revertOnError', type: 'bool' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'target', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + ]}, + { type: 'uint256' }, + { type: 'bytes' }, + ], + [metaTransactions as any, nonce, encodedSignature as `0x${string}`] + ); + + // Prepend function selector + return executeSelector + encodedParams.slice(2); +} + diff --git a/pkg/wallet/src/signer/magic.ts b/pkg/wallet/src/signer/magic.ts new file mode 100644 index 0000000000..704ccb6541 --- /dev/null +++ b/pkg/wallet/src/signer/magic.ts @@ -0,0 +1,163 @@ +/** + * Magic TEE Signer + * Alternative signer implementation using Magic's TEE (Trusted Execution Environment) + */ + +import type { User } from '@imtbl/auth'; +import type { Signer } from './signer'; + +export interface MagicTEESignerConfig { + /** Magic TEE API base URL */ + magicTeeBasePath: string; + /** Magic publishable API key */ + magicPublishableApiKey: string; + /** Magic provider ID */ + magicProviderId: string; + /** Authenticated user */ + authenticatedUser: User; +} + +const CHAIN_IDENTIFIER = 'ETH'; + +interface UserWallet { + userIdentifier: string; + walletAddress: string; +} + +/** + * Magic TEE Signer + * Uses Magic's TEE for signing without exposing private keys + */ +export class MagicTEESigner implements Signer { + private config: MagicTEESignerConfig; + + private userWallet: UserWallet | null = null; + + private createWalletPromise: Promise | null = null; + + constructor(config: MagicTEESignerConfig) { + this.config = config; + } + + /** + * Gets the user's wallet address + */ + async getAddress(): Promise { + const userWallet = await this.getUserWallet(); + return userWallet.walletAddress; + } + + /** + * Signs a message using Magic TEE + */ + async signMessage(message: string | Uint8Array): Promise { + // Ensure wallet is created + await this.getUserWallet(); + + const messageToSign = message instanceof Uint8Array + ? `0x${Array.from(message).map((b) => b.toString(16).padStart(2, '0')).join('')}` + : message; + + // Convert string to base64 + // messageToSign is always a string at this point + const messageBase64 = btoa( + new TextEncoder().encode(messageToSign).reduce((data, byte) => data + String.fromCharCode(byte), ''), + ); + + const response = await fetch( + `${this.config.magicTeeBasePath}/v1/wallet/sign/message`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Magic-API-Key': this.config.magicPublishableApiKey, + 'X-OIDC-Provider-ID': this.config.magicProviderId, + 'X-Magic-Chain': CHAIN_IDENTIFIER, + Authorization: `Bearer ${this.config.authenticatedUser.id_token}`, + }, + body: JSON.stringify({ + message_base64: messageBase64, + }), + }, + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `MagicTEE: Failed to sign message with status ${response.status}: ${JSON.stringify(errorData)}`, + ); + } + + const data = await response.json(); + return data.signature; + } + + /** + * Gets or creates the user's wallet + */ + private async getUserWallet(): Promise { + let { userWallet } = this; + if (!userWallet) { + userWallet = await this.createWallet(); + } + + // Check if the user has changed since the last createWallet request was made + const userIdentifier = this.config.authenticatedUser.profile?.sub; + if (userIdentifier && userWallet.userIdentifier !== userIdentifier) { + userWallet = await this.createWallet(); + } + + return userWallet; + } + + /** + * Creates a wallet via Magic TEE API + * The createWallet endpoint is idempotent, so it can be called multiple times + */ + private async createWallet(): Promise { + if (this.createWalletPromise) { + return this.createWalletPromise; + } + + this.createWalletPromise = (async () => { + this.userWallet = null; + + const response = await fetch( + `${this.config.magicTeeBasePath}/v1/wallet`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Magic-API-Key': this.config.magicPublishableApiKey, + 'X-OIDC-Provider-ID': this.config.magicProviderId, + 'X-Magic-Chain': CHAIN_IDENTIFIER, + Authorization: `Bearer ${this.config.authenticatedUser.id_token}`, + }, + }, + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `MagicTEE: Failed to initialise EOA with status ${response.status}: ${JSON.stringify(errorData)}`, + ); + } + + const data = await response.json(); + const userIdentifier = this.config.authenticatedUser.profile?.sub || ''; + + this.userWallet = { + userIdentifier, + walletAddress: data.public_address, + }; + + return this.userWallet; + })(); + + try { + return await this.createWalletPromise; + } finally { + this.createWalletPromise = null; + } + } +} diff --git a/pkg/wallet/src/signer/signer.ts b/pkg/wallet/src/signer/signer.ts new file mode 100644 index 0000000000..78595c4f5c --- /dev/null +++ b/pkg/wallet/src/signer/signer.ts @@ -0,0 +1,23 @@ +/** + * Minimal signer interface for message signing + * Compatible with ethers Signer, viem WalletClient, or custom implementations + */ +export interface Signer { + /** + * Gets the signer's address + */ + getAddress(): Promise; + + /** + * Signs a message + * @param message - Message to sign (string or Uint8Array bytes) + * @returns Promise resolving to signature hex string + */ + signMessage(message: string | Uint8Array): Promise; + + /** + * Signs typed data (for eth_signTypedData_v4) + * Optional - if not provided, will use signMessage with hashed typed data + */ + signTypedData?(domain: any, types: any, value: any): Promise; +} diff --git a/pkg/wallet/src/signer/signing.ts b/pkg/wallet/src/signer/signing.ts new file mode 100644 index 0000000000..43138db528 --- /dev/null +++ b/pkg/wallet/src/signer/signing.ts @@ -0,0 +1,97 @@ +/** + * Signing helpers for ERC-191 and EIP-712 + * Uses viem internally for EIP-712 hashing (tree-shakeable) + */ + +import { hashTypedData, hashMessage, keccak256, hexToBytes } from 'viem'; +import type { TypedDataPayload } from '../relayer'; +import { packSignatures } from '../sequence'; +import type { Signer } from './signer'; +import { removeHexPrefix } from '../utils/hex'; + +/** + * Signs an ERC-191 message with Immutable wallet sub-digest + * Uses viem's hashMessage for ERC-191, then adds custom sub-digest logic + */ +export async function signERC191Message( + chainId: bigint, + payload: string, + signer: Signer, + walletAddress: string +): Promise { + // Use viem's hashMessage for ERC-191 prefix: \x19Ethereum Signed Message:\n{len(message)}{message} + // viem's hashMessage accepts a string directly + const digest = hashMessage(payload); + + // Create sub-digest with chain ID and wallet address (custom Immutable logic) + // Format: \x19\x01{chainId}{walletAddress}{digest} + const subDigest = encodeMessageSubDigest(chainId, walletAddress, digest); + const subDigestHash = keccak256(`0x${Buffer.from(subDigest, 'utf8').toString('hex')}` as `0x${string}`); + + const hashBytes = hexToBytes(subDigestHash); + + return signer.signMessage(hashBytes); +} + +/** + * Signs EIP-712 typed data + */ +export async function signTypedData( + typedData: TypedDataPayload, + relayerSignature: string, + chainId: bigint, + walletAddress: string, + signer: Signer +): Promise { + // Use viem's hashTypedData (handles full EIP-712 spec) + // Note: viem expects types without EIP712Domain, but our payload includes it + const { EIP712Domain, ...types } = typedData.types; + + // Convert domain chainId to number/bigint if it's a string + const domain = { + ...typedData.domain, + chainId: typeof typedData.domain.chainId === 'string' + ? Number(typedData.domain.chainId) + : typedData.domain.chainId, + }; + + const typedDataHash = hashTypedData({ + domain: domain as any, + types: types as any, + primaryType: typedData.primaryType, + message: typedData.message, + }); + + // Create sub-digest (custom Immutable logic) + const subDigest = encodeMessageSubDigest(chainId, walletAddress, typedDataHash); + const subDigestHash = keccak256(`0x${Buffer.from(subDigest, 'utf8').toString('hex')}` as `0x${string}`); + + // Convert hex to bytes for signing + const hashBytes = hexToBytes(subDigestHash); + + // Sign the hash + const eoaSignature = await signer.signMessage(hashBytes); + const eoaAddress = await signer.getAddress(); + + // Pack signatures + return packSignatures(eoaSignature, eoaAddress, relayerSignature); +} + +/** + * Encodes message sub-digest: \x19\x01{chainId}{walletAddress}{digest} + * Custom Immutable logic for wallet contract authentication + * This is specific to Immutable's wallet contract implementation + */ + +export function encodeMessageSubDigest( + chainId: bigint, + walletAddress: string, + digest: string +): string { + const prefix = '\x19\x01'; + const chainIdHex = chainId.toString(16).padStart(64, '0'); + const address = removeHexPrefix(walletAddress).toLowerCase(); + const digestHex = removeHexPrefix(digest); + + return prefix + chainIdHex + address + digestHex; +} diff --git a/pkg/wallet/src/utils/abi.ts b/pkg/wallet/src/utils/abi.ts new file mode 100644 index 0000000000..cab7f87205 --- /dev/null +++ b/pkg/wallet/src/utils/abi.ts @@ -0,0 +1,17 @@ +/** + * ABI utility functions + */ + +import { keccak256, toHex } from 'viem'; + +/** + * Gets function selector from function signature + * Replaces deprecated getFunctionSelector from viem + * + * @param signature Function signature (e.g., 'transfer(address,uint256)') + * @returns Function selector (first 4 bytes of keccak256 hash) + */ +export function getFunctionSelector(signature: string): `0x${string}` { + return keccak256(toHex(signature)).slice(0, 10) as `0x${string}`; +} + diff --git a/pkg/wallet/src/utils/chain.ts b/pkg/wallet/src/utils/chain.ts new file mode 100644 index 0000000000..e6890beb29 --- /dev/null +++ b/pkg/wallet/src/utils/chain.ts @@ -0,0 +1,11 @@ +/** + * Chain-related utility functions + */ + +/** + * Gets EIP-155 chain ID format (eip155:chainId) + */ +export function getEip155ChainId(chainId: number): string { + return `eip155:${chainId}`; +} + diff --git a/pkg/wallet/src/utils/hex.ts b/pkg/wallet/src/utils/hex.ts new file mode 100644 index 0000000000..0003dcd7ed --- /dev/null +++ b/pkg/wallet/src/utils/hex.ts @@ -0,0 +1,54 @@ +/** + * Hex string utilities + * Simple helpers for hex manipulation (viem handles most conversions) + */ + +import { hexToString as viemHexToString } from 'viem'; + +/** + * Removes 0x prefix from hex string if present + * Used for manual hex string manipulation (e.g., Sequence encoding) + */ +export function removeHexPrefix(hex: string): string { + return hex.startsWith('0x') ? hex.slice(2) : hex; +} + +/** + * Cleans an address (removes prefix, lowercases) + * Used for Sequence signature encoding + */ +export function cleanAddress(addr: string): string { + return removeHexPrefix(addr).toLowerCase(); +} + +/** + * Cleans a signature (removes prefix, validates length) + * Used for Sequence signature encoding + */ +export function cleanSignature(sig: string, expectedLength?: number): string { + const cleaned = removeHexPrefix(sig); + if (expectedLength && cleaned.length !== expectedLength) { + throw new Error(`Invalid signature length: expected ${expectedLength} hex chars, got ${cleaned.length}`); + } + return cleaned; +} + +/** + * Converts hex string to string + * Uses viem's hexToString directly - viem handles hex validation + * Note: personal_sign messages are typically already strings or properly formatted hex + */ +export function hexToString(hex: string): string { + // If not hex, return as-is (might already be a string) + if (!hex.startsWith('0x')) { + return hex; + } + + try { + return viemHexToString(hex as `0x${string}`); + } catch { + // If viem can't decode (invalid UTF-8), return hex as-is + return hex; + } +} + diff --git a/pkg/wallet/src/utils/http-client.ts b/pkg/wallet/src/utils/http-client.ts new file mode 100644 index 0000000000..c2680720d6 --- /dev/null +++ b/pkg/wallet/src/utils/http-client.ts @@ -0,0 +1,74 @@ +/** + * Minimal HTTP client utility + * Consolidates fetch patterns across API clients + */ + +export interface AuthenticatedFetchOptions { + method?: string; + body?: any; + token?: string; + headers?: Record; +} + +/** + * Makes an authenticated HTTP request + */ +export async function authenticatedFetch( + url: string, + options: AuthenticatedFetchOptions = {} +): Promise { + const headers: Record = { + 'Content-Type': 'application/json', + ...options.headers, + }; + + if (options.token) { + headers['Authorization'] = `Bearer ${options.token}`; + } + + const response = await fetch(url, { + method: options.method || 'GET', + headers, + ...(options.body && { body: JSON.stringify(options.body) }), + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`HTTP ${response.status}: ${text}`); + } + + return response.json(); +} + +/** + * Makes a JSON-RPC request (for relayer) + */ +export async function jsonRpcRequest( + url: string, + method: string, + params: any[] = [], + token?: string +): Promise { + const body = { + jsonrpc: '2.0', + id: 1, + method, + params, + }; + + const response = await authenticatedFetch<{ result?: T; error?: { message: string; code?: number } }>( + url, + { + method: 'POST', + body, + token, + } + ); + + if (response.error) { + throw new Error(`RPC error: ${response.error.message} (code: ${response.error.code || 'unknown'})`); + } + + return response.result as T; +} + diff --git a/pkg/wallet/tsconfig.json b/pkg/wallet/tsconfig.json new file mode 100644 index 0000000000..b97dfb1ce4 --- /dev/null +++ b/pkg/wallet/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDirs": ["src"], + "customConditions": ["development"] + }, + "include": ["src"], + "exclude": [ + "node_modules", + "dist" + ] +} + diff --git a/pkg/wallet/typedoc.json b/pkg/wallet/typedoc.json new file mode 100644 index 0000000000..8e5152304f --- /dev/null +++ b/pkg/wallet/typedoc.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "name": "wallet" +} + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f559ae5bdd..a82e8fdbdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,7 +132,7 @@ importers: version: 0.25.21(@emotion/react@11.11.3(@types/react@18.3.12)(react@18.3.1))(@rive-app/react-canvas-lite@4.9.0(react@18.3.1))(embla-carousel-react@8.1.5(react@18.3.1))(framer-motion@11.18.2(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@imtbl/sdk': specifier: latest - version: 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) + version: 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) next: specifier: 14.2.25 version: 14.2.25(@babel/core@7.26.9)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -777,7 +777,7 @@ importers: version: 18.3.1(react@18.3.1) wagmi: specifier: ^2.11.3 - version: 2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) + version: 2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) devDependencies: '@playwright/test': specifier: ^1.45.2 @@ -835,7 +835,7 @@ importers: version: 18.3.1(react@18.3.1) wagmi: specifier: ^2.11.3 - version: 2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) + version: 2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) devDependencies: '@playwright/test': specifier: ^1.45.3 @@ -2520,6 +2520,107 @@ importers: specifier: ^8.0.1 version: 8.0.1(typescript@5.6.2) + pkg/auth: + dependencies: + '@imtbl/config': + specifier: workspace:* + version: link:../../packages/config + '@imtbl/metrics': + specifier: workspace:* + version: link:../../packages/internal/metrics + oidc-client-ts: + specifier: 3.3.0 + version: 3.3.0 + devDependencies: + '@swc/core': + specifier: ^1.3.36 + version: 1.9.3(@swc/helpers@0.5.13) + '@swc/jest': + specifier: ^0.2.37 + version: 0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13)) + '@types/jest': + specifier: ^29.4.3 + version: 29.5.14 + '@types/node': + specifier: ^18.14.2 + version: 18.15.13 + '@typescript-eslint/eslint-plugin': + specifier: ^5.57.1 + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': + specifier: ^5.57.1 + version: 5.62.0(eslint@8.57.0)(typescript@5.6.2) + eslint: + specifier: ^8.40.0 + version: 8.57.0 + jest: + specifier: ^29.4.3 + version: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) + jest-environment-jsdom: + specifier: ^29.4.3 + version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + prettier: + specifier: ^2.8.7 + version: 2.8.8 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2) + tsup: + specifier: 8.3.0 + version: 8.3.0(@swc/core@1.9.3(@swc/helpers@0.5.13))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.6.2)(yaml@2.5.0) + typescript: + specifier: ^5.6.2 + version: 5.6.2 + + pkg/wallet: + dependencies: + '@imtbl/auth': + specifier: workspace:* + version: link:../auth + viem: + specifier: ^2.39.0 + version: 2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + devDependencies: + '@swc/core': + specifier: ^1.3.36 + version: 1.9.3(@swc/helpers@0.5.13) + '@swc/jest': + specifier: ^0.2.37 + version: 0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13)) + '@types/jest': + specifier: ^29.4.3 + version: 29.5.14 + '@types/node': + specifier: ^18.14.2 + version: 18.15.13 + '@typescript-eslint/eslint-plugin': + specifier: ^5.57.1 + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': + specifier: ^5.57.1 + version: 5.62.0(eslint@8.57.0)(typescript@5.6.2) + eslint: + specifier: ^8.40.0 + version: 8.57.0 + jest: + specifier: ^29.4.3 + version: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) + jest-environment-jsdom: + specifier: ^29.4.3 + version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + prettier: + specifier: ^2.8.7 + version: 2.8.8 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2) + tsup: + specifier: 8.3.0 + version: 8.3.0(@swc/core@1.9.3(@swc/helpers@0.5.13))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.6.2)(yaml@2.5.0) + typescript: + specifier: ^5.6.2 + version: 5.6.2 + sdk: dependencies: '@imtbl/blockchain-data': @@ -2724,6 +2825,9 @@ packages: '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -5024,6 +5128,10 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} @@ -5033,6 +5141,10 @@ packages: '@noble/curves@1.4.2': resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.2.0': resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} @@ -5048,6 +5160,10 @@ packages: resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@noble/secp256k1@1.7.1': resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -6100,18 +6216,27 @@ packages: '@scure/base@1.1.7': resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + '@scure/bip32@1.1.5': resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + '@scure/bip39@1.1.1': resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@segment/analytics-core@1.3.0': resolution: {integrity: sha512-ujScWZH49NK1hYlp2/EMw45nOPEh+pmTydAnR6gSkRNucZD4fuinvpPL03rmFCw8ibaMuKLAdgPJfQ0gkLKZ5A==} @@ -7750,6 +7875,17 @@ packages: zod: optional: true + abitype@1.1.0: + resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -11701,6 +11837,11 @@ packages: peerDependencies: ws: '*' + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -13446,6 +13587,14 @@ packages: outvariant@1.4.0: resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + ox@0.9.6: + resolution: {integrity: sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + oxc-resolver@1.11.0: resolution: {integrity: sha512-N3qMse2AM7uST8PaiUMXZkcACyGAMN073tomyvzHTICSzaOqKHvVS0IZ3vj/OqoE140QP4CyOiWmgC1Hw5Urmg==} @@ -15407,6 +15556,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -16617,6 +16767,14 @@ packages: typescript: optional: true + viem@2.39.0: + resolution: {integrity: sha512-rCN+IfnMESlrg/iPyyVL+M9NS/BHzyyNy72470tFmbTuscY3iPaZGMtJDcHKKV8TC6HV9DjWk0zWX6cpu0juyA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + vite-plugin-node-polyfills@0.16.0: resolution: {integrity: sha512-uj1ymOmk7TliMxiivmXokpMY5gVMBpFPSZPLQSCv/LjkJGGKwyLjpbFL64dbYZEdFSUQ3tM7pbrxNh25yvhqOA==} peerDependencies: @@ -17010,6 +17168,18 @@ packages: utf-8-validate: optional: true + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.5.0: resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} engines: {node: '>=10.0.0'} @@ -17198,6 +17368,8 @@ snapshots: '@adraffy/ens-normalize@1.10.1': {} + '@adraffy/ens-normalize@1.11.1': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.2.1': @@ -20061,12 +20233,12 @@ snapshots: - supports-color - utf-8-validate - '@imtbl/checkout-sdk@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/checkout-sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@imtbl/blockchain-data': 2.1.11 '@imtbl/bridge-sdk': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@imtbl/config': 2.1.11 - '@imtbl/dex-sdk': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/dex-sdk': 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) '@imtbl/generated-clients': 2.1.11 '@imtbl/metrics': 2.1.11 '@imtbl/orderbook': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20132,12 +20304,12 @@ snapshots: - typescript - utf-8-validate - '@imtbl/dex-sdk@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/dex-sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@imtbl/config': 2.1.11 '@uniswap/sdk-core': 3.2.3 - '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) - '@uniswap/v3-sdk': 3.10.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@uniswap/v3-sdk': 3.10.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -20226,10 +20398,10 @@ snapshots: - encoding - supports-color - '@imtbl/sdk@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@imtbl/blockchain-data': 2.1.11 - '@imtbl/checkout-sdk': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/checkout-sdk': 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) '@imtbl/config': 2.1.11 '@imtbl/minting-backend': 2.1.11 '@imtbl/orderbook': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20438,6 +20610,43 @@ snapshots: - ts-node - utf-8-validate + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0(node-notifier@8.0.2) + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.13 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + optionalDependencies: + node-notifier: 8.0.2 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 @@ -21363,6 +21572,8 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/ciphers@1.3.0': {} + '@noble/curves@1.2.0': dependencies: '@noble/hashes': 1.3.2 @@ -21375,6 +21586,10 @@ snapshots: dependencies: '@noble/hashes': 1.4.0 + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/hashes@1.2.0': {} '@noble/hashes@1.3.2': {} @@ -21383,6 +21598,8 @@ snapshots: '@noble/hashes@1.5.0': {} + '@noble/hashes@1.8.0': {} + '@noble/secp256k1@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -21581,7 +21798,7 @@ snapshots: '@nrwl/tao@19.7.3(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13))': dependencies: nx: 19.7.3(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13)) - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' @@ -21613,7 +21830,7 @@ snapshots: nx: 19.7.3(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13)) semver: 7.7.1 tmp: 0.2.3 - tslib: 2.6.3 + tslib: 2.7.0 yargs-parser: 21.1.1 '@nx/js@19.7.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(nx@19.7.3(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13)))(typescript@5.6.2)': @@ -21696,7 +21913,7 @@ snapshots: chalk: 4.1.2 enquirer: 2.3.6 nx: 19.7.3(@swc-node/register@1.10.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.6.2))(@swc/core@1.9.3(@swc/helpers@0.5.13)) - tslib: 2.6.3 + tslib: 2.7.0 yargs-parser: 21.1.1 transitivePeerDependencies: - '@swc-node/register' @@ -22902,7 +23119,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.22.1 - viem: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + viem: 2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - typescript @@ -22913,6 +23130,8 @@ snapshots: '@scure/base@1.1.7': {} + '@scure/base@1.2.6': {} + '@scure/bip32@1.1.5': dependencies: '@noble/hashes': 1.2.0 @@ -22925,6 +23144,12 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@scure/bip39@1.1.1': dependencies: '@noble/hashes': 1.2.0 @@ -22935,6 +23160,11 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@segment/analytics-core@1.3.0': dependencies: '@lukeed/uuid': 2.0.1 @@ -24551,7 +24781,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.1 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 @@ -24570,7 +24800,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.1 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 @@ -24589,7 +24819,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.1 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 @@ -24749,6 +24979,17 @@ snapshots: transitivePeerDependencies: - hardhat + '@uniswap/swap-router-contracts@1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))': + dependencies: + '@openzeppelin/contracts': 3.4.2 + '@uniswap/v2-core': 1.0.1 + '@uniswap/v3-core': 1.0.0 + '@uniswap/v3-periphery': 1.4.4 + dotenv: 14.3.2 + hardhat-watcher: 2.5.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) + transitivePeerDependencies: + - hardhat + '@uniswap/v2-core@1.0.1': {} '@uniswap/v3-core@1.0.0': {} @@ -24782,6 +25023,19 @@ snapshots: transitivePeerDependencies: - hardhat + '@uniswap/v3-sdk@3.10.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))': + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/solidity': 5.7.0 + '@uniswap/sdk-core': 4.0.6 + '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@uniswap/v3-periphery': 1.4.3 + '@uniswap/v3-staker': 1.0.0 + tiny-invariant: 1.3.1 + tiny-warning: 1.0.3 + transitivePeerDependencies: + - hardhat + '@uniswap/v3-staker@1.0.0': dependencies: '@openzeppelin/contracts': 3.4.2 @@ -24799,17 +25053,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@wagmi/connectors@5.1.1(@types/react@18.3.12)(@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10))': + '@wagmi/connectors@5.1.1(@types/react@18.3.12)(@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10))': dependencies: '@coinbase/wallet-sdk': 4.0.4 '@metamask/sdk': 0.26.5(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(utf-8-validate@5.0.10) '@safe-global/safe-apps-provider': 0.18.3(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) - '@wagmi/core': 2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@wagmi/core': 2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) '@walletconnect/ethereum-provider': 2.13.0(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10) '@walletconnect/modal': 2.6.2(@types/react@18.3.12)(react@18.3.1) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' - viem: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + viem: 2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -24836,11 +25090,11 @@ snapshots: - utf-8-validate - zod - '@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10))': + '@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@5.6.2) - viem: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + viem: 2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) zustand: 4.4.1(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1) optionalDependencies: '@tanstack/query-core': 5.51.15 @@ -25248,7 +25502,7 @@ snapshots: '@yarnpkg/parsers@3.0.0-rc.46': dependencies: js-yaml: 3.14.1 - tslib: 2.6.3 + tslib: 2.7.0 '@zkochan/js-yaml@0.0.7': dependencies: @@ -25267,6 +25521,10 @@ snapshots: optionalDependencies: typescript: 5.6.2 + abitype@1.1.0(typescript@5.6.2): + optionalDependencies: + typescript: 5.6.2 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -26913,6 +27171,21 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + create-jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 @@ -26943,6 +27216,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 @@ -29728,6 +30016,11 @@ snapshots: chokidar: 3.6.0 hardhat: 2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10) + hardhat-watcher@2.5.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)): + dependencies: + chokidar: 3.6.0 + hardhat: 2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10) + hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.7.0 @@ -30578,6 +30871,10 @@ snapshots: dependencies: ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isows@1.0.7(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + dependencies: + ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@4.0.3: @@ -30784,11 +31081,11 @@ snapshots: jest-cli@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0) exit: 0.1.2 import-local: 3.1.0 jest-config: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) @@ -30847,11 +31144,11 @@ snapshots: jest-cli@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0) exit: 0.1.2 import-local: 3.1.0 jest-config: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) @@ -31963,7 +32260,7 @@ snapshots: jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) '@jest/types': 29.6.3 import-local: 3.1.0 jest-cli: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) @@ -32005,7 +32302,7 @@ snapshots: jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) '@jest/types': 29.6.3 import-local: 3.1.0 jest-cli: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) @@ -33689,6 +33986,21 @@ snapshots: outvariant@1.4.0: {} + ox@0.9.6(typescript@5.6.2): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.1.0(typescript@5.6.2) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - zod + oxc-resolver@1.11.0: optionalDependencies: '@oxc-resolver/binding-darwin-arm64': 1.11.0 @@ -36607,7 +36919,7 @@ snapshots: synckit@0.8.5: dependencies: '@pkgr/utils': 2.4.2 - tslib: 2.6.3 + tslib: 2.7.0 syncpack@13.0.0(typescript@5.6.2): dependencies: @@ -37670,6 +37982,23 @@ snapshots: - utf-8-validate - zod + viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.1.0(typescript@5.6.2) + isows: 1.0.7(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + ox: 0.9.6(typescript@5.6.2) + ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-plugin-node-polyfills@0.16.0(rollup@4.28.0)(vite@5.4.7(@types/node@18.15.13)(lightningcss@1.21.5)(terser@5.34.1)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.28.0) @@ -37709,14 +38038,14 @@ snapshots: dependencies: xml-name-validator: 4.0.0 - wagmi@2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)): + wagmi@2.12.1(@tanstack/query-core@5.51.15)(@tanstack/react-query@5.51.15(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)): dependencies: '@tanstack/react-query': 5.51.15(react@18.3.1) - '@wagmi/connectors': 5.1.1(@types/react@18.3.12)(@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) - '@wagmi/core': 2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@wagmi/connectors': 5.1.1(@types/react@18.3.12)(@wagmi/core@2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.28.0)(typescript@5.6.2)(utf-8-validate@5.0.10)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) + '@wagmi/core': 2.13.1(@tanstack/query-core@5.51.15)(@types/react@18.3.12)(immer@9.0.21)(react@18.3.1)(typescript@5.6.2)(viem@2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)) react: 18.3.1 use-sync-external-store: 1.2.0(react@18.3.1) - viem: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + viem: 2.39.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -38207,6 +38536,11 @@ snapshots: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + ws@8.5.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.8 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9249cc5540..ed5f607eae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -23,6 +23,8 @@ packages: - "packages/game-bridge" - "packages/webhook/sdk" - "packages/minting-backend/sdk" + - "pkg/auth" + - "pkg/wallet" - "tests/**" - "examples/passport/**" - "examples/orderbook/**" From f7f105e4ba4a416a3080748adfccf543a44494eb Mon Sep 17 00:00:00 2001 From: Alex Connolly <25735635+alex-connolly@users.noreply.github.com> Date: Fri, 14 Nov 2025 06:51:24 +1100 Subject: [PATCH 2/5] wip --- pkg/auth/README.md | 136 ++++++++++++-------- pkg/auth/src/index.ts | 80 +----------- pkg/wallet/README.md | 204 ++++++++++++++++++++++-------- pkg/wallet/src/eip6963.ts | 9 +- pkg/wallet/src/ejection.ts | 3 +- pkg/wallet/src/index.ts | 69 +--------- pkg/wallet/src/metatransaction.ts | 51 ++------ pkg/wallet/src/provider.ts | 105 ++++++--------- pkg/wallet/src/sequence.ts | 17 +-- pkg/wallet/src/signer/signing.ts | 46 ++----- 10 files changed, 314 insertions(+), 406 deletions(-) diff --git a/pkg/auth/README.md b/pkg/auth/README.md index 4a5c8902cb..71193acdf4 100644 --- a/pkg/auth/README.md +++ b/pkg/auth/README.md @@ -1,6 +1,6 @@ # @imtbl/auth -Minimal authentication package for Immutable Passport. Provides OAuth-based authentication that can be used standalone or passed to other SDK packages for enhanced functionality. +Minimal OAuth-based authentication package for Immutable Passport. Provides a thin wrapper around `oidc-client-ts` for OAuth/OIDC authentication flows. ## Installation @@ -12,9 +12,7 @@ pnpm add @imtbl/auth yarn add @imtbl/auth ``` -## Usage - -### Basic Authentication with Popup +## Quick Start ```typescript import { Auth } from '@imtbl/auth'; @@ -22,69 +20,83 @@ import { Auth } from '@imtbl/auth'; const auth = new Auth({ clientId: 'your-client-id', redirectUri: 'https://your-app.com/callback', - environment: 'production', }); // Login with popup const user = await auth.loginPopup(); console.log(user?.profile.email); -console.log(user?.access_token); // Direct access to tokens +console.log(user?.access_token); // Get current user const currentUser = await auth.getUser(); -if (currentUser) { - console.log(currentUser.id_token); // Access ID token - console.log(currentUser.expired); // Check if expired -} // Logout await auth.logout(); ``` -### With Redirect Flow +## Usage + +### Popup Flow + +```typescript +const auth = new Auth({ + clientId: 'your-client-id', + redirectUri: 'https://your-app.com/callback', +}); + +const user = await auth.loginPopup(); +if (user) { + console.log(user.access_token); + console.log(user.id_token); + console.log(user.profile.email); +} +``` + +### Redirect Flow ```typescript // On your login page await auth.loginRedirect(); -// On your callback page +// On your callback page (e.g., /callback) const user = await auth.handleRedirect(); -console.log(user?.profile.email); -console.log(user?.access_token); // Direct access to tokens +if (user) { + console.log(user.access_token); +} ``` -### Standalone Usage - -The auth package can be used completely independently: +### Direct Login Methods ```typescript -import { Auth } from '@imtbl/auth'; +// Google login +await auth.loginPopup({ directLoginMethod: 'google' }); -const auth = new Auth({ clientId: '...', redirectUri: '...' }); -const user = await auth.loginPopup(); -const accessToken = user?.access_token; +// Apple login +await auth.loginPopup({ directLoginMethod: 'apple' }); -// Use token for API calls -fetch('https://api.example.com/data', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, +// Email login +await auth.loginPopup({ + directLoginMethod: 'email', + email: 'user@example.com', }); ``` -### With Wallet Package +### Token Management ```typescript -import { Auth } from '@imtbl/auth'; -import { Wallet } from '@imtbl/wallet'; - -const auth = new Auth({ clientId: '...', redirectUri: '...' }); -await auth.loginPopup(); const user = await auth.getUser(); -// Pass authenticated user to wallet for enhanced features -const wallet = new Wallet({ authenticatedUser: user }); -const provider = await wallet.connect(); +if (user) { + // Check if token is expired + if (user.expired) { + await auth.refreshToken(); + } + + // Access tokens directly + const accessToken = user.access_token; + const idToken = user.id_token; + const refreshToken = user.refresh_token; +} ``` ## API Reference @@ -97,38 +109,54 @@ const provider = await wallet.connect(); new Auth(config: AuthConfig) ``` +**Config Options:** +- `clientId` (required): OAuth client ID +- `redirectUri` (required): OAuth redirect URI +- `popupRedirectUri` (optional): Custom popup redirect URI (defaults to `redirectUri`) +- `logoutRedirectUri` (optional): Custom logout redirect URI +- `scope` (optional): OAuth scope (defaults to `'openid profile email'`) + #### Methods -- `loginPopup(options?: LoginOptions): Promise` - Login with popup window +- `loginPopup(options?: LoginOptions): Promise` - Login with popup window - `loginRedirect(options?: LoginOptions): Promise` - Login with redirect flow -- `handleRedirect(): Promise` - Handle OAuth callback after redirect -- `getUser(): Promise` - Gets current authenticated user -- `logout(): Promise` - Logs out current user (with redirect) -- `logoutSilent(): Promise` - Logs out silently (without redirect) -- `refreshToken(): Promise` - Refreshes access token if expired +- `handleRedirect(): Promise` - Handle OAuth callback after redirect +- `getUser(): Promise` - Get current authenticated user +- `logout(): Promise` - Logout with redirect +- `logoutSilent(): Promise` - Logout silently (without redirect) +- `refreshToken(): Promise` - Refresh access token if expired ### Types -- `OidcUser` - OIDC user object from oidc-client-ts (includes `id_token`, `access_token`, `refresh_token`, `profile`, `expired`, `expires_at`, `scope`, etc.) +- `User` - OIDC user object from `oidc-client-ts` (includes `id_token`, `access_token`, `refresh_token`, `profile`, `expired`, `expires_at`, `scope`, etc.) - `AuthConfig` - Configuration options -- `LoginOptions` - Login options (direct method, email, marketing consent) - works for both popup and redirect flows +- `LoginOptions` - Login options: + - `directLoginMethod?: string` - Direct login method (`'google'`, `'apple'`, `'email'`) + - `email?: string` - Email address (required when `directLoginMethod` is `'email'`) + - `marketingConsent?: 'opted_in' | 'unsubscribed'` - Marketing consent status -### Accessing Tokens +## Integration with Wallet Package -Since methods return `OidcUser` directly, you can access tokens and user info: +The auth package can be used standalone or passed to the wallet package for automatic authentication: ```typescript -const user = await auth.getUser(); -if (user) { - const accessToken = user.access_token; - const idToken = user.id_token; - const email = user.profile.email; - const isExpired = user.expired; - const expiresAt = user.expires_at; -} +import { Auth } from '@imtbl/auth'; +import { connectWallet } from '@imtbl/wallet'; + +const auth = new Auth({ clientId: '...', redirectUri: '...' }); + +// Pass auth client - login handled automatically when needed +const provider = await connectWallet({ auth }); + +// User will be prompted to login automatically when required +const accounts = await provider.request({ method: 'eth_requestAccounts' }); ``` +## Storage + +- **Browser**: Uses `localStorage` for token storage +- **SSR**: Uses `InMemoryWebStorage` (tokens not persisted) + ## License Apache-2.0 - diff --git a/pkg/auth/src/index.ts b/pkg/auth/src/index.ts index 42a6879b4f..d10bef442b 100644 --- a/pkg/auth/src/index.ts +++ b/pkg/auth/src/index.ts @@ -1,10 +1,3 @@ -/** - * @imtbl/auth - Minimal authentication package - * - * Provides OAuth-based authentication for Immutable Passport. - * Can be used standalone or passed to wallet/Passport clients for enhanced functionality. - */ - import { UserManager, UserManagerSettings, @@ -13,7 +6,6 @@ import { InMemoryWebStorage, } from 'oidc-client-ts'; -// Re-export User from oidc-client-ts as the main User type export type { User } from 'oidc-client-ts'; /** @@ -44,17 +36,14 @@ export interface LoginOptions { marketingConsent?: 'opted_in' | 'unsubscribed'; } -/** - * Builds UserManagerSettings from AuthConfig - */ function buildUserManagerSettings(config: AuthConfig): UserManagerSettings { const authDomain = 'https://auth.immutable.com'; - + // Use localStorage in browser, InMemoryWebStorage for SSR - const store: Storage = typeof window !== 'undefined' - ? window.localStorage + const store: Storage = typeof window !== 'undefined' + ? window.localStorage : new InMemoryWebStorage(); - + const userStore = new WebStorageStateStore({ store }); // Build logout endpoint @@ -88,9 +77,6 @@ function buildUserManagerSettings(config: AuthConfig): UserManagerSettings { return settings; } -/** - * Builds extra query parameters for login - */ function buildExtraQueryParams(options?: LoginOptions): Record { const params: Record = {}; @@ -109,33 +95,11 @@ function buildExtraQueryParams(options?: LoginOptions): Record { } /** - * Minimal authentication client - * - * @example - * ```typescript - * import { Auth } from '@imtbl/auth'; - * - * const auth = new Auth({ - * clientId: 'your-client-id', - * redirectUri: 'https://your-app.com/callback' - * }); - * - * // Login with popup - * const user = await auth.loginPopup(); - * console.log(user?.profile.email); - * - * // Or login with redirect - * await auth.loginRedirect(); - * // Then on callback page: - * const user = await auth.handleRedirect(); - * ``` + * OAuth-based authentication client for Immutable Passport */ export class Auth { private userManager: UserManager; - /** - * Creates a new Auth instance - */ constructor(config: AuthConfig) { if (!config.clientId) { throw new Error('clientId is required'); @@ -147,11 +111,6 @@ export class Auth { this.userManager = new UserManager(buildUserManagerSettings(config)); } - /** - * Initiates login with popup window - * @param options Optional login options - * @returns Promise resolving to User, or null if cancelled - */ async loginPopup(options?: LoginOptions): Promise { try { await this.userManager.clearStaleState(); @@ -167,7 +126,7 @@ export class Auth { return oidcUser; } catch (error) { - // Return null if popup was closed by user, otherwise pass through error + // Return null if popup was closed by user if (error instanceof Error && error.message.includes('Popup closed')) { return null; } @@ -175,10 +134,6 @@ export class Auth { } } - /** - * Initiates login with redirect - * @param options Optional login options - */ async loginRedirect(options?: LoginOptions): Promise { await this.userManager.clearStaleState(); @@ -186,20 +141,11 @@ export class Auth { await this.userManager.signinRedirect({ extraQueryParams }); } - /** - * Handles OAuth callback after redirect - * Call this on your callback page after loginRedirect() - * @returns Promise resolving to User, or null if no user - */ async handleRedirect(): Promise { const oidcUser = await this.userManager.signinCallback(); return oidcUser || null; } - /** - * Gets the current authenticated user - * @returns Promise resolving to User, or null if not authenticated - */ async getUser(): Promise { try { const oidcUser = await this.userManager.getUser(); @@ -208,35 +154,21 @@ export class Auth { } return oidcUser; } catch (error) { - // If user not found, return null (not an error) if (error instanceof Error && error.message.includes('user not found')) { return null; } - // Pass through other errors - they're already well-formed throw error; } } - /** - * Logs out the current user - */ async logout(): Promise { - // Pass through errors - oidc-client-ts errors are already clear await this.userManager.signoutRedirect(); } - /** - * Logs out silently (without redirect) - */ async logoutSilent(): Promise { - // Pass through errors - oidc-client-ts errors are already clear await this.userManager.signoutSilent(); } - /** - * Refreshes the access token if expired - * Called automatically by oidc-client-ts when needed, but can be called manually - */ async refreshToken(): Promise { const oidcUser = await this.userManager.getUser(); if (!oidcUser) { diff --git a/pkg/wallet/README.md b/pkg/wallet/README.md index 69ee27de26..cd87abc452 100644 --- a/pkg/wallet/README.md +++ b/pkg/wallet/README.md @@ -1,6 +1,6 @@ # @imtbl/wallet -Minimal wallet package for Immutable zkEVM. Provides an EIP-1193 compatible provider that can be used standalone (zero config) or with authenticated user for enhanced functionality. +EIP-1193 compatible wallet provider for Immutable zkEVM. Works standalone (zero-config) or with authentication for enhanced features. ## Installation @@ -12,103 +12,196 @@ pnpm add @imtbl/wallet yarn add @imtbl/wallet ``` -## Usage +## Quick Start ### Zero Config (Standalone) -Works immediately without any configuration: +```typescript +import { connectWallet } from '@imtbl/wallet'; + +// Works immediately - uses default Immutable chains +const provider = await connectWallet(); + +// Use with any EIP-1193 compatible library +const accounts = await provider.request({ method: 'eth_requestAccounts' }); +``` + +### With Authentication ```typescript -import { Wallet } from '@imtbl/wallet'; +import { Auth } from '@imtbl/auth'; +import { connectWallet } from '@imtbl/wallet'; + +const auth = new Auth({ clientId: '...', redirectUri: '...' }); + +// Pass auth client - login handled automatically when needed +const provider = await connectWallet({ auth }); -const wallet = new Wallet(); -const provider = await wallet.connect(); +// User will be prompted to login automatically +const accounts = await provider.request({ method: 'eth_requestAccounts' }); +``` + +## Usage Examples -// Use with viem +### With viem + +```typescript +import { connectWallet } from '@imtbl/wallet'; import { createWalletClient, custom } from 'viem'; + +const provider = await connectWallet(); const client = createWalletClient({ transport: custom(provider), }); -// Use with ethers -import { BrowserProvider } from 'ethers'; -const ethersProvider = new BrowserProvider(provider); +const accounts = await client.requestAddresses(); +const balance = await client.getBalance({ address: accounts[0] }); ``` -### With Authentication (Enhanced Features) - -Pass an authenticated user from `@imtbl/auth` for enhanced features: +### With ethers ```typescript -import { Auth } from '@imtbl/auth'; -import { Wallet } from '@imtbl/wallet'; +import { connectWallet } from '@imtbl/wallet'; +import { BrowserProvider } from 'ethers'; -// Authenticate first -const auth = new Auth({ clientId: '...', redirectUri: '...' }); -await auth.login(); -const user = await auth.getUser(); +const provider = await connectWallet(); +const ethersProvider = new BrowserProvider(provider); + +const signer = await ethersProvider.getSigner(); +const address = await signer.getAddress(); +const balance = await ethersProvider.getBalance(address); +``` -// Create wallet with auth context -const wallet = new Wallet({ authenticatedUser: user }); -const provider = await wallet.connect(); +### Custom Chain Configuration -// Now has enhanced features (user context, linked wallets via Passport) +```typescript +const provider = await connectWallet({ + chains: [{ + chainId: 13371, + rpcUrl: 'https://rpc.immutable.com', + relayerUrl: 'https://api.immutable.com/relayer-mr', + apiUrl: 'https://api.immutable.com', + name: 'Immutable zkEVM', + }], + initialChainId: 13371, +}); ``` -### Basic Wallet Operations +### Sending Transactions ```typescript -const wallet = new Wallet(); -const provider = await wallet.connect(); +const provider = await connectWallet({ auth }); -// Get wallet address -const address = await wallet.getAddress(); +// Request accounts (triggers login if needed) +const accounts = await provider.request({ method: 'eth_requestAccounts' }); + +// Send transaction +const txHash = await provider.request({ + method: 'eth_sendTransaction', + params: [{ + from: accounts[0], + to: '0x...', + value: '0x0', + data: '0x...', + }], +}); +``` -// Get chain ID -const chainId = await wallet.getChainId(); +### Signing Messages -// Check connection -const isConnected = await wallet.isConnected(); +```typescript +// Sign personal message (ERC-191) +const signature = await provider.request({ + method: 'personal_sign', + params: ['0x48656c6c6f20576f726c64', accounts[0]], +}); -// Use EIP-1193 methods -const accounts = await provider.request({ method: 'eth_requestAccounts' }); -const balance = await provider.request({ - method: 'eth_getBalance', - params: [accounts[0], 'latest'], +// Sign typed data (EIP-712) +const typedDataSignature = await provider.request({ + method: 'eth_signTypedData_v4', + params: [accounts[0], { + domain: { name: 'MyApp', version: '1', chainId: 13371 }, + types: { Message: [{ name: 'content', type: 'string' }] }, + message: { content: 'Hello World' }, + }], }); ``` ## API Reference -### `Wallet` - -#### Constructor +### `connectWallet` ```typescript -new Wallet(config?: WalletConfig) +function connectWallet(config?: WalletConfig): Promise ``` -If `config` is omitted, works in zero-config mode. Pass `authenticatedUser` for enhanced features. +Creates and returns an EIP-1193 compatible provider. -#### Methods +**Config Options:** +- `chains?: ChainConfig[]` - Chain configurations (defaults to Immutable testnet + mainnet) +- `initialChainId?: number` - Initial chain ID (defaults to first chain) +- `auth?: Auth` - Auth client for automatic login +- `popupOverlayOptions?: { disableGenericPopupOverlay?: boolean; disableBlockedPopupOverlay?: boolean }` - Overlay options +- `announceProvider?: boolean` - Announce via EIP-6963 (default: `true`) -- `connect(options?: ConnectOptions): Promise` - Connects and returns EIP-1193 provider -- `getAddress(): Promise` - Gets connected wallet address -- `getChainId(): Promise` - Gets current chain ID -- `isConnected(): Promise` - Checks if wallet is connected +### `Provider` (EIP-1193) -### Types +The returned provider implements the standard EIP-1193 interface: -- `Provider` - EIP-1193 compatible provider interface -- `RequestArguments` - JSON-RPC request format -- `WalletConfig` - Optional configuration -- `ConnectOptions` - Connection options -- `AuthenticatedUser` - Authenticated user from `@imtbl/auth` +```typescript +interface Provider { + request(args: RequestArguments): Promise; + on(event: string, listener: (...args: any[]) => void): void; + removeListener(event: string, listener: (...args: any[]) => void): void; +} +``` -## EIP-1193 Compatibility +**Supported Methods:** +- `eth_requestAccounts` - Request wallet connection (triggers login if needed) +- `eth_accounts` - Get connected accounts +- `eth_sendTransaction` - Send transaction +- `eth_sign` - Sign message (deprecated, use `personal_sign`) +- `personal_sign` - Sign personal message (ERC-191) +- `eth_signTypedData_v4` - Sign typed data (EIP-712) +- `eth_chainId` - Get current chain ID +- `wallet_switchEthereumChain` - Switch chain +- `eth_getBalance` - Get balance +- `eth_call` - Call contract +- `eth_getTransactionReceipt` - Get transaction receipt +- `im_signEjectionTransaction` - Sign ejection transaction (wallet recovery) +- `im_addSessionActivity` - Add session activity (analytics) + +**Events:** +- `accountsChanged` - Emitted when accounts change + +## Architecture + +### Zero Config Mode + +When no auth client is provided, the wallet works in read-only mode: +- Can query chain state (`eth_call`, `eth_getBalance`, etc.) +- Cannot send transactions or sign messages +- Login is required for signing operations + +### Authenticated Mode + +When an auth client is provided: +- Login is triggered automatically when signing operations are requested +- Magic TEE signer is initialized automatically +- Wallet address is registered automatically on first use +- Full transaction and signing capabilities available + +### Meta-Transactions + +All transactions are sent as meta-transactions through Immutable's relayer: +1. Transaction is validated by Guardian API +2. Transaction is signed with Sequence signature format +3. Transaction is submitted via relayer +4. Relayer handles gas fees (paid in IMX token) -The provider returned by `connect()` is fully EIP-1193 compatible and works with: +## EIP-1193 Compatibility +The provider is fully EIP-1193 compatible and works with: - **viem** - `createWalletClient({ transport: custom(provider) })` - **ethers** - `new BrowserProvider(provider)` - **web3.js** - `new Web3(provider)` @@ -117,4 +210,3 @@ The provider returned by `connect()` is fully EIP-1193 compatible and works with ## License Apache-2.0 - diff --git a/pkg/wallet/src/eip6963.ts b/pkg/wallet/src/eip6963.ts index 77cb86507c..bb91dded82 100644 --- a/pkg/wallet/src/eip6963.ts +++ b/pkg/wallet/src/eip6963.ts @@ -35,8 +35,10 @@ export interface EIP6963AnnounceProviderEvent extends CustomEvent { - const r = (Math.random() * 16) | 0; - const v = c === 'x' ? r : (r & 0x3) | 0x8; + // eslint-disable-next-line @typescript-eslint/no-bitwise + const r = Math.floor(Math.random() * 16); + // eslint-disable-next-line @typescript-eslint/no-bitwise + const v = c === 'x' ? r : (r % 4) + 8; return v.toString(16); }); } @@ -68,5 +70,4 @@ export function announceProvider(detail: EIP6963ProviderDetail): void { // Listen for requests and re-announce const handler = () => window.dispatchEvent(event); window.addEventListener('eip6963:requestProvider', handler); -} - +} \ No newline at end of file diff --git a/pkg/wallet/src/ejection.ts b/pkg/wallet/src/ejection.ts index 77fea14cba..38abbbbf76 100644 --- a/pkg/wallet/src/ejection.ts +++ b/pkg/wallet/src/ejection.ts @@ -29,8 +29,7 @@ export async function prepareAndSignEjectionTransaction({ zkEvmAddress: string; chainId: number; }): Promise { - // Validate required fields - if (!transactionRequest.to) { + if (!transactionRequest.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, 'im_signEjectionTransaction requires a "to" field' diff --git a/pkg/wallet/src/index.ts b/pkg/wallet/src/index.ts index fbb59ba10c..1ff22de4d7 100644 --- a/pkg/wallet/src/index.ts +++ b/pkg/wallet/src/index.ts @@ -1,16 +1,6 @@ -/** - * @imtbl/wallet - Minimal wallet package - * - * Provides EIP-1193 compatible wallet provider for Immutable zkEVM. - * Can be used standalone or with authenticated user for enhanced functionality. - */ - import type { Auth } from '@imtbl/auth'; import type { Provider, ChainConfig } from './provider'; -/** - * Wallet configuration (optional) - */ export interface WalletConfig { /** Chain configurations - if omitted, uses default Immutable chains */ chains?: ChainConfig[]; @@ -27,74 +17,24 @@ export interface WalletConfig { announceProvider?: boolean; } -/** - * Signer interface - library agnostic - * Compatible with ethers Signer, viem WalletClient, or custom implementations - */ export type { Signer } from './signer/signer'; - -/** - * Connects to Immutable Passport wallet and returns an EIP-1193 compatible provider. - * - * Returns provider directly - use with any EIP-1193 compatible library. - * Signer is handled automatically internally when user is authenticated. - * - * @example - * ```typescript - * // Standalone (zero config - uses default Immutable chains) - * import { connectWallet } from '@imtbl/wallet'; - * - * const provider = await connectWallet(); - * - * // With custom chain configuration - * const provider = await connectWallet({ - * chains: [{ - * chainId: 13371, - * rpcUrl: 'https://rpc.immutable.com', - * relayerUrl: 'https://api.immutable.com/relayer-mr', - * apiUrl: 'https://api.immutable.com', - * name: 'Immutable zkEVM', - * }], - * }); - * - * // Use with viem - * import { createWalletClient, custom } from 'viem'; - * const client = createWalletClient({ - * transport: custom(provider), - * }); - * - * // Request accounts - * const accounts = await provider.request({ method: 'eth_requestAccounts' }); - * - * // With authentication (enhanced features) - * // Login/getUser handled automatically under the hood - * import { Auth } from '@imtbl/auth'; - * - * const auth = new Auth({ clientId: '...', redirectUri: '...' }); - * const provider = await connectWallet({ auth }); - * // Auth client handles login/getUser automatically when needed - * // Signer is automatically initialized internally - * ``` - */ import { PassportEVMProvider } from './provider'; import { announceProvider, passportProviderInfo } from './eip6963'; export async function connectWallet( config?: WalletConfig ): Promise { - // Use provided chains or default Immutable chains const chains = config?.chains || getDefaultChains(); - + const provider = new PassportEVMProvider({ chains, initialChainId: config?.initialChainId, - authenticatedUser: undefined, // Will be set automatically when login is triggered - auth: config?.auth, // Pass auth client for automatic login + authenticatedUser: undefined, + auth: config?.auth, popupOverlayOptions: config?.popupOverlayOptions, }); - // Announce provider via EIP-6963 if requested if (config?.announceProvider !== false) { announceProvider({ info: passportProviderInfo, @@ -105,9 +45,6 @@ export async function connectWallet( return provider; } -/** - * Gets default Immutable chain configurations - */ function getDefaultChains(): ChainConfig[] { return [ { diff --git a/pkg/wallet/src/metatransaction.ts b/pkg/wallet/src/metatransaction.ts index bce3eabd86..c78a0775a1 100644 --- a/pkg/wallet/src/metatransaction.ts +++ b/pkg/wallet/src/metatransaction.ts @@ -1,6 +1,3 @@ -/** - * Meta-transaction building and signing utilities - */ import type { User } from '@imtbl/auth'; import { toHex, createPublicClient, http } from 'viem'; @@ -12,9 +9,6 @@ import { getFunctionSelector } from './utils/abi'; import { signMetaTransactions } from './sequence'; import { getEip155ChainId } from './utils/chain'; -/** - * Meta-transaction structure (all fields required) - */ export interface MetaTransaction { gasLimit: bigint; target: `0x${string}`; @@ -24,9 +18,6 @@ export interface MetaTransaction { revertOnError: boolean; } -/** - * Transaction request (from eth_sendTransaction) - */ export interface TransactionRequest { to: string; data?: string; @@ -35,21 +26,20 @@ export interface TransactionRequest { chainId?: number; } -/** - * Builds a MetaTransaction from TransactionRequest with defaults - * Exported for use in ejection transactions and other cases where - * we need to convert TransactionRequest to MetaTransaction directly - */ export function buildMetaTransaction( request: TransactionRequest ): MetaTransaction { + if (!request.to) { + throw new Error('TransactionRequest.to is required'); + } + return { - target: (request.to || '0x0000000000000000000000000000000000000000') as `0x${string}`, - value: typeof request.value === 'string' - ? BigInt(request.value) + target: request.to as `0x${string}`, + value: typeof request.value === 'string' + ? BigInt(request.value) : (request.value ?? BigInt(0)), data: (request.data || '0x') as `0x${string}`, - gasLimit: BigInt(0), // Default, relayer handles gas + gasLimit: BigInt(0), delegateCall: false, revertOnError: true, }; @@ -58,8 +48,8 @@ export function buildMetaTransaction( /** * Gets nonce from smart contract wallet via RPC - * Returns 0 if wallet is not deployed (BAD_DATA error) - * Encodes nonce with space (space in upper 160 bits, nonce in lower 96 bits) + * Encodes nonce with space: space in upper 160 bits, nonce in lower 96 bits + * Returns 0 if wallet is not deployed */ export async function getNonce( rpcUrl: string, @@ -67,12 +57,7 @@ export async function getNonce( nonceSpace?: bigint ): Promise { const space = nonceSpace || BigInt(0); - - // Read nonce from wallet contract using eth_call - // Function signature: readNonce(uint256 space) returns (uint256) const functionSelector = getFunctionSelector('readNonce(uint256)'); - - // Encode the space parameter const spaceHex = toHex(space, { size: 32 }); const data = functionSelector + spaceHex.slice(2); @@ -87,15 +72,12 @@ export async function getNonce( if (result?.data && result.data !== '0x') { const nonce = BigInt(result.data); - // Encode nonce with space (space in upper 160 bits, nonce in lower 96 bits) const shiftedSpace = space * (BigInt(2) ** BigInt(96)); return nonce + shiftedSpace; } - - // If result is 0x or empty, wallet might not be deployed + return BigInt(0); } catch (error: any) { - // BAD_DATA error usually means wallet not deployed if (error?.message?.includes('BAD_DATA') || error?.message?.includes('execution reverted')) { return BigInt(0); } @@ -106,8 +88,7 @@ export async function getNonce( /** * Builds meta-transactions array with fee transaction - * Returns transactions directly in normalized format (no intermediate type) - * Also returns the nonce used for signing + * Fetches nonce and fee option in parallel, then builds final transaction array */ export async function buildMetaTransactions( transactionRequest: TransactionRequest, @@ -118,28 +99,24 @@ export async function buildMetaTransactions( user: User, nonceSpace?: bigint ): Promise<{ transactions: [MetaTransaction, ...MetaTransaction[]]; nonce: bigint }> { - if (!transactionRequest.to) { + if (!transactionRequest.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, 'eth_sendTransaction requires a "to" field' ); } - // Build transaction for fee estimation (nonce doesn't matter for fees) const txForFeeEstimation = buildMetaTransaction(transactionRequest); - // Get nonce and fee option in parallel const [nonce, feeOption] = await Promise.all([ getNonce(rpcUrl, zkevmAddress, nonceSpace), relayerClient.getFeeOption(zkevmAddress, [txForFeeEstimation], chainId, user), ]); - // Build final transactions with valid nonce const metaTransactions: [MetaTransaction, ...MetaTransaction[]] = [ buildMetaTransaction(transactionRequest), ]; - // Add fee transaction if fee is non-zero const feeValue = BigInt(feeOption.tokenPrice); if (feeValue !== BigInt(0)) { metaTransactions.push({ @@ -157,7 +134,7 @@ export async function buildMetaTransactions( /** * Validates and signs meta-transactions in parallel - * Consolidates the common pattern used in handleSendTransaction and deployWallet + * Guardian validation and Sequence signing happen concurrently for performance */ export async function validateAndSignTransaction( metaTransactions: MetaTransaction[], diff --git a/pkg/wallet/src/provider.ts b/pkg/wallet/src/provider.ts index efbae860a6..05e227f811 100644 --- a/pkg/wallet/src/provider.ts +++ b/pkg/wallet/src/provider.ts @@ -1,6 +1,5 @@ /** - * PassportEVMProvider - Minimal EIP-1193 provider for Immutable EVM chains - * + * EIP-1193 compatible provider for Immutable zkEVM */ import type { User, Auth } from '@imtbl/auth'; @@ -20,12 +19,8 @@ import { buildMetaTransactions, validateAndSignTransaction } from './metatransac import { prepareAndSignEjectionTransaction } from './ejection'; import type { TransactionRequest } from './metatransaction'; -/** - * Gets passport domain based on chain configuration - */ function getPassportDomain(chains: ChainConfig[]): string { - // If any chain uses sandbox API, use sandbox passport domain - const isSandbox = chains.some(chain => + const isSandbox = chains.some(chain => chain.apiUrl.includes('sandbox') || chain.apiUrl.includes('testnet') ); return isSandbox @@ -237,8 +232,7 @@ export class PassportEVMProvider implements Provider { } /** - * Initializes MagicTEESigner automatically when user is authenticated - * Signer is handled internally - clients should not need to provide their own + * Initializes Magic TEE signer when user is authenticated */ private initializeSigner(): void { if (!this.authenticatedUser) { @@ -268,8 +262,7 @@ export class PassportEVMProvider implements Provider { } /** - * Sets the auth client (internal use only) - * For automatic login/getUser - handled internally by provider + * Sets auth client for automatic login * @internal */ private setAuth(auth: Auth): void { @@ -285,13 +278,10 @@ export class PassportEVMProvider implements Provider { } /** - * Sets the signer (internal use only) - * Signer is automatically initialized when user is authenticated. - * This method is kept for backward compatibility but should not be used by clients. - * + * Sets signer (internal use only) * @internal */ - setSigner(signer: Signer): void { + private setSigner(signer: Signer): void { this.signer = signer; } @@ -387,15 +377,13 @@ export class PassportEVMProvider implements Provider { } /** - * Ensures user is authenticated, automatically triggers login if auth client is provided + * Ensures user is authenticated, triggers login automatically if auth client provided */ private async ensureAuthenticated(): Promise { - // If already authenticated, return if (this.authenticatedUser) { return; } - // If auth client provided, automatically trigger login if (this.auth) { const user = await this.auth.loginPopup(); if (user) { @@ -404,34 +392,12 @@ export class PassportEVMProvider implements Provider { } } - // If still not authenticated, throw error throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, 'User not authenticated. Please provide an auth client or login first.' ); } - /** - * Ensures everything is ready for signing operations - * Automatically triggers login if auth client is provided - */ - private async ensureSigningReady(): Promise<{ - address: string; - signer: Signer; - relayerClient: RelayerClient; - guardianClient: GuardianClient; - }> { - // Ensure authenticated (will auto-login if auth client provided) - await this.ensureAuthenticated(); - - // Ensure wallet address (will register if needed) - const address = await this.ensureWalletAddress(); - - // Ensure signer is set - const signer = this.ensureSigner(); - - return { address, signer, relayerClient: this.relayerClient, guardianClient: this.guardianClient }; - } /** * Handles eth_requestAccounts @@ -480,18 +446,18 @@ export class PassportEVMProvider implements Provider { * Handles eth_sendTransaction */ private async handleSendTransaction(params: any[]): Promise { + await this.ensureAuthenticated(); const address = await this.ensureWalletAddress(); - const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + const signer = this.ensureSigner(); const transactionRequest = params[0]; - if (!transactionRequest?.to) { + if (!transactionRequest?.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, 'Transaction must include to field' ); } - // Validate address format if (!isAddress(transactionRequest.to)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, @@ -507,7 +473,7 @@ export class PassportEVMProvider implements Provider { value: transactionRequest.value ? BigInt(transactionRequest.value) : undefined, }, this.currentRpcUrl, - relayerClient, + this.relayerClient, address, this.currentChainId, this.authenticatedUser! @@ -522,13 +488,13 @@ export class PassportEVMProvider implements Provider { chainId, address, signer, - guardianClient, + this.guardianClient, this.authenticatedUser!, false ); // Send to relayer - const relayerId = await relayerClient.ethSendTransaction( + const relayerId = await this.relayerClient.ethSendTransaction( address, // to is the wallet address signedTransactionData, this.currentChainId, @@ -536,13 +502,13 @@ export class PassportEVMProvider implements Provider { ); // Poll for transaction hash - return this.pollTransaction(relayerId, relayerClient); + return this.pollTransaction(relayerId); } /** * Polls relayer for transaction hash */ - private async pollTransaction(relayerId: string, relayerClient: RelayerClient): Promise { + private async pollTransaction(relayerId: string): Promise { if (!this.authenticatedUser) { throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, @@ -554,7 +520,7 @@ export class PassportEVMProvider implements Provider { const delayMs = 1000; for (let i = 0; i < maxAttempts; i++) { - const tx = await relayerClient.imGetTransactionByHash(relayerId, this.authenticatedUser); + const tx = await this.relayerClient.imGetTransactionByHash(relayerId, this.authenticatedUser); if (tx.status === 'SUCCESSFUL' || tx.status === 'SUBMITTED') { return tx.hash; @@ -580,7 +546,9 @@ export class PassportEVMProvider implements Provider { * Handles personal_sign */ private async handlePersonalSign(params: any[]): Promise { - const { address, signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + await this.ensureAuthenticated(); + const address = await this.ensureWalletAddress(); + const signer = this.ensureSigner(); const message: string = params[0]; const fromAddress: string = params[1]; @@ -613,12 +581,12 @@ export class PassportEVMProvider implements Provider { const chainId = BigInt(this.currentChainId); // Evaluate with guardian (passes wallet address for confirmation) - await guardianClient.evaluateERC191Message(payload, address, this.currentChainId, this.authenticatedUser!); + await this.guardianClient.evaluateERC191Message(payload, address, this.currentChainId, this.authenticatedUser!); // Sign with EOA and get relayer signature in parallel const [eoaSignature, relayerSignature] = await Promise.all([ signERC191Message(chainId, payload, signer, address), - relayerClient.imSign(address, payload, this.currentChainId, this.authenticatedUser!), + this.relayerClient.imSign(address, payload, this.currentChainId, this.authenticatedUser!), ]); const eoaAddress = await signer.getAddress(); @@ -631,8 +599,9 @@ export class PassportEVMProvider implements Provider { * Deploys the smart contract wallet by sending a zero-value transaction */ private async deployWallet(): Promise { + await this.ensureAuthenticated(); const address = await this.ensureWalletAddress(); - const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + const signer = this.ensureSigner(); // Build meta-transactions for deployment (zero-value transaction to self) const { transactions: metaTransactions, nonce } = await buildMetaTransactions( @@ -642,7 +611,7 @@ export class PassportEVMProvider implements Provider { value: BigInt(0), }, this.currentRpcUrl, - relayerClient, + this.relayerClient, address, this.currentChainId, this.authenticatedUser! @@ -657,13 +626,13 @@ export class PassportEVMProvider implements Provider { chainId, address, signer, - guardianClient, + this.guardianClient, this.authenticatedUser!, false ); // Send to relayer - const relayerId = await relayerClient.ethSendTransaction( + const relayerId = await this.relayerClient.ethSendTransaction( address, signedTransactionData, this.currentChainId, @@ -671,14 +640,16 @@ export class PassportEVMProvider implements Provider { ); // Wait for deployment to complete - await this.pollTransaction(relayerId, relayerClient); + await this.pollTransaction(relayerId); } /** * Handles eth_signTypedData_v4 */ private async handleSignTypedDataV4(params: any[]): Promise { - const { address, signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + await this.ensureAuthenticated(); + const address = await this.ensureWalletAddress(); + const signer = this.ensureSigner(); const fromAddress: string = params[0]; const typedDataParam: string | object = params[1]; @@ -730,10 +701,10 @@ export class PassportEVMProvider implements Provider { const chainId = BigInt(this.currentChainId); // Evaluate with guardian (passes wallet address for confirmation) - await guardianClient.evaluateEIP712Message(typedData, address, this.currentChainId, this.authenticatedUser!); + await this.guardianClient.evaluateEIP712Message(typedData, address, this.currentChainId, this.authenticatedUser!); // Get relayer signature - const relayerSignature = await relayerClient.imSignTypedData(address, typedData, this.currentChainId, this.authenticatedUser!); + const relayerSignature = await this.relayerClient.imSignTypedData(address, typedData, this.currentChainId, this.authenticatedUser!); // If signer has signTypedData method, use it (ethers/viem signers) if (signer.signTypedData) { @@ -809,8 +780,9 @@ export class PassportEVMProvider implements Provider { data: string; chainId: string; }> { + await this.ensureAuthenticated(); const address = await this.ensureWalletAddress(); - const { signer } = await this.ensureSigningReady(); + const signer = this.ensureSigner(); if (!params || params.length !== 1) { throw new JsonRpcError( @@ -917,7 +889,8 @@ export class PassportEVMProvider implements Provider { delay?: number ): Promise { try { - const { signer, relayerClient, guardianClient } = await this.ensureSigningReady(); + await this.ensureAuthenticated(); + const signer = this.ensureSigner(); // Encode function call (simple function with no parameters) const functionSelector = getFunctionSelector(`${functionName}()`); @@ -930,7 +903,7 @@ export class PassportEVMProvider implements Provider { data: functionSelector, }, this.currentRpcUrl, - relayerClient, + this.relayerClient, walletAddress, this.currentChainId, this.authenticatedUser!, @@ -946,13 +919,13 @@ export class PassportEVMProvider implements Provider { chainId, walletAddress, signer, - guardianClient, + this.guardianClient, this.authenticatedUser!, true // isBackgroundTransaction ); // Send to relayer - await relayerClient.ethSendTransaction( + await this.relayerClient.ethSendTransaction( walletAddress, signedTransactionData, this.currentChainId, diff --git a/pkg/wallet/src/sequence.ts b/pkg/wallet/src/sequence.ts index c94080bc92..78b12592d0 100644 --- a/pkg/wallet/src/sequence.ts +++ b/pkg/wallet/src/sequence.ts @@ -1,16 +1,6 @@ /** * Sequence signature encoding/decoding - * Minimal implementation replacing @0xsequence/core dependency - * - * Sequence signature format: - * - version (1 byte): 0x00 or 0x01 - * - threshold (1 byte): number of signatures required - * - signers: array of signer data - * - isDynamic (1 bit): whether signer is dynamic - * - unrecovered (1 bit): whether signature is unrecovered - * - weight (6 bits): weight of signer - * - signature (65 bytes): actual signature - * - address (20 bytes, optional): signer address + * Minimal implementation for Immutable wallet signature format */ import { isAddress, keccak256, encodeAbiParameters, fromHex } from 'viem'; @@ -209,11 +199,10 @@ export function packSignatures( }, ]; - // Sort signers by address (if address present) + // Sort signers by address (lexicographic comparison) const sortedSigners = combinedSigners.sort((a, b) => { if (!a.address || !b.address) return 0; - const diff = BigInt(a.address) - BigInt(b.address); - return diff === 0n ? 0 : diff < 0n ? -1 : 1; + return a.address.toLowerCase().localeCompare(b.address.toLowerCase()); }); // Re-encode with threshold 2 diff --git a/pkg/wallet/src/signer/signing.ts b/pkg/wallet/src/signer/signing.ts index 43138db528..19d72a8e81 100644 --- a/pkg/wallet/src/signer/signing.ts +++ b/pkg/wallet/src/signer/signing.ts @@ -1,8 +1,3 @@ -/** - * Signing helpers for ERC-191 and EIP-712 - * Uses viem internally for EIP-712 hashing (tree-shakeable) - */ - import { hashTypedData, hashMessage, keccak256, hexToBytes } from 'viem'; import type { TypedDataPayload } from '../relayer'; import { packSignatures } from '../sequence'; @@ -10,8 +5,8 @@ import type { Signer } from './signer'; import { removeHexPrefix } from '../utils/hex'; /** - * Signs an ERC-191 message with Immutable wallet sub-digest - * Uses viem's hashMessage for ERC-191, then adds custom sub-digest logic + * Signs ERC-191 message with Immutable wallet sub-digest + * Applies custom sub-digest format: \x19\x01{chainId}{walletAddress}{digest} */ export async function signERC191Message( chainId: bigint, @@ -19,22 +14,17 @@ export async function signERC191Message( signer: Signer, walletAddress: string ): Promise { - // Use viem's hashMessage for ERC-191 prefix: \x19Ethereum Signed Message:\n{len(message)}{message} - // viem's hashMessage accepts a string directly const digest = hashMessage(payload); - - // Create sub-digest with chain ID and wallet address (custom Immutable logic) - // Format: \x19\x01{chainId}{walletAddress}{digest} const subDigest = encodeMessageSubDigest(chainId, walletAddress, digest); const subDigestHash = keccak256(`0x${Buffer.from(subDigest, 'utf8').toString('hex')}` as `0x${string}`); - const hashBytes = hexToBytes(subDigestHash); - + return signer.signMessage(hashBytes); } /** - * Signs EIP-712 typed data + * Signs EIP-712 typed data with Immutable wallet sub-digest + * Packs EOA signature with relayer signature using Sequence format */ export async function signTypedData( typedData: TypedDataPayload, @@ -43,15 +33,12 @@ export async function signTypedData( walletAddress: string, signer: Signer ): Promise { - // Use viem's hashTypedData (handles full EIP-712 spec) - // Note: viem expects types without EIP712Domain, but our payload includes it const { EIP712Domain, ...types } = typedData.types; - - // Convert domain chainId to number/bigint if it's a string + const domain = { ...typedData.domain, - chainId: typeof typedData.domain.chainId === 'string' - ? Number(typedData.domain.chainId) + chainId: typeof typedData.domain.chainId === 'string' + ? Number(typedData.domain.chainId) : typedData.domain.chainId, }; @@ -61,28 +48,21 @@ export async function signTypedData( primaryType: typedData.primaryType, message: typedData.message, }); - - // Create sub-digest (custom Immutable logic) + const subDigest = encodeMessageSubDigest(chainId, walletAddress, typedDataHash); const subDigestHash = keccak256(`0x${Buffer.from(subDigest, 'utf8').toString('hex')}` as `0x${string}`); - - // Convert hex to bytes for signing const hashBytes = hexToBytes(subDigestHash); - - // Sign the hash + const eoaSignature = await signer.signMessage(hashBytes); const eoaAddress = await signer.getAddress(); - - // Pack signatures + return packSignatures(eoaSignature, eoaAddress, relayerSignature); } /** - * Encodes message sub-digest: \x19\x01{chainId}{walletAddress}{digest} - * Custom Immutable logic for wallet contract authentication - * This is specific to Immutable's wallet contract implementation + * Encodes message sub-digest for Immutable wallet contract authentication + * Format: \x19\x01{chainId}{walletAddress}{digest} */ - export function encodeMessageSubDigest( chainId: bigint, walletAddress: string, From 2dabf74a1664873cb283979d7f9547ed0885bfcd Mon Sep 17 00:00:00 2001 From: Alex Connolly <25735635+alex-connolly@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:28:34 +1100 Subject: [PATCH 3/5] wip --- pkg/auth/.eslintrc.cjs | 9 + pkg/auth/src/index.ts | 2 +- pkg/wallet/.eslintrc.cjs | 9 + pkg/wallet/src/api.ts | 9 +- pkg/wallet/src/confirmation/confirmation.ts | 17 +- pkg/wallet/src/confirmation/overlay.ts | 10 +- pkg/wallet/src/confirmation/popup.ts | 1 - pkg/wallet/src/eip6963.ts | 18 +- pkg/wallet/src/ejection.ts | 10 +- pkg/wallet/src/guardian.ts | 90 ++++-- pkg/wallet/src/index.ts | 48 ++-- pkg/wallet/src/metatransaction.ts | 30 +- pkg/wallet/src/provider.ts | 286 +++++++++++--------- pkg/wallet/src/relayer.ts | 61 ++--- pkg/wallet/src/sequence.ts | 63 +++-- pkg/wallet/src/signer/signing.ts | 29 +- pkg/wallet/src/types.ts | 22 ++ pkg/wallet/src/utils/abi.ts | 3 +- pkg/wallet/src/utils/chain.ts | 1 - pkg/wallet/src/utils/hex.ts | 22 -- pkg/wallet/src/utils/http-client.ts | 26 +- pkg/wallet/src/utils/subdigest.ts | 18 ++ 22 files changed, 442 insertions(+), 342 deletions(-) create mode 100644 pkg/auth/.eslintrc.cjs create mode 100644 pkg/wallet/.eslintrc.cjs create mode 100644 pkg/wallet/src/types.ts create mode 100644 pkg/wallet/src/utils/subdigest.ts diff --git a/pkg/auth/.eslintrc.cjs b/pkg/auth/.eslintrc.cjs new file mode 100644 index 0000000000..73d3ddcbfa --- /dev/null +++ b/pkg/auth/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + extends: ['../../.eslintrc'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, +}; + diff --git a/pkg/auth/src/index.ts b/pkg/auth/src/index.ts index d10bef442b..090854974c 100644 --- a/pkg/auth/src/index.ts +++ b/pkg/auth/src/index.ts @@ -161,7 +161,7 @@ export class Auth { } } - async logout(): Promise { + async logoutRedirect(): Promise { await this.userManager.signoutRedirect(); } diff --git a/pkg/wallet/.eslintrc.cjs b/pkg/wallet/.eslintrc.cjs new file mode 100644 index 0000000000..73d3ddcbfa --- /dev/null +++ b/pkg/wallet/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + extends: ['../../.eslintrc'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, +}; + diff --git a/pkg/wallet/src/api.ts b/pkg/wallet/src/api.ts index dd4e3b416d..b148dfeea9 100644 --- a/pkg/wallet/src/api.ts +++ b/pkg/wallet/src/api.ts @@ -25,7 +25,7 @@ export class ApiClient { */ async listChains(): Promise> { const data = await authenticatedFetch<{ result?: Array<{ id: string; name: string }> }>( - `${this.config.apiUrl}/v1/chains` + `${this.config.apiUrl}/v1/chains`, ); return data.result || []; } @@ -37,7 +37,7 @@ export class ApiClient { chainName: string, ethereumAddress: string, ethereumSignature: string, - user: User + user: User, ): Promise { // User is guaranteed to be authenticated when this is called // (ensured by ensureAuthenticated() in provider) @@ -52,7 +52,7 @@ export class ApiClient { ethereum_signature: ethereumSignature, }, token: user.access_token, - } + }, ); return data.counterfactual_address; } @@ -63,11 +63,10 @@ export class ApiClient { async getChainName(chainId: number): Promise { const chains = await this.listChains(); const eipChainId = getEip155ChainId(chainId); - const chain = chains.find(c => c.id === eipChainId); + const chain = chains.find((c) => c.id === eipChainId); if (!chain) { throw new Error(`Chain ${chainId} not found`); } return chain.name; } } - diff --git a/pkg/wallet/src/confirmation/confirmation.ts b/pkg/wallet/src/confirmation/confirmation.ts index 8f47427aed..6920f876b3 100644 --- a/pkg/wallet/src/confirmation/confirmation.ts +++ b/pkg/wallet/src/confirmation/confirmation.ts @@ -25,6 +25,8 @@ export interface ConfirmationScreenConfig { passportDomain: string; /** Overlay options */ popupOverlayOptions?: PopupOverlayOptions; + /** Enable cross-SDK bridge mode - skips opening confirmation popups */ + crossSdkBridgeEnabled?: boolean; } /** @@ -32,10 +34,15 @@ export interface ConfirmationScreenConfig { */ export class ConfirmationScreen { private config: ConfirmationScreenConfig; + private confirmationWindow: Window | undefined; + private popupOptions: { width: number; height: number } | undefined; + private overlay: ConfirmationOverlay | undefined; + private overlayClosed: boolean; + private timer: ReturnType | undefined; constructor(config: ConfirmationScreenConfig) { @@ -172,6 +179,11 @@ export class ConfirmationScreen { * Show loading screen */ loading(popupOptions?: { width: number; height: number }) { + if (this.config.crossSdkBridgeEnabled) { + // Skip opening confirmation window if cross-SDK bridge is enabled + return; + } + this.popupOptions = popupOptions; try { @@ -183,13 +195,13 @@ export class ConfirmationScreen { }); this.overlay = new ConfirmationOverlay( this.config.popupOverlayOptions || {}, - false + false, ); } catch (error) { // If an error is thrown here then the popup is blocked this.overlay = new ConfirmationOverlay( this.config.popupOverlayOptions || {}, - true + true, ); } @@ -273,4 +285,3 @@ export class ConfirmationScreen { } catch { /* Empty */ } } } - diff --git a/pkg/wallet/src/confirmation/overlay.ts b/pkg/wallet/src/confirmation/overlay.ts index 2749d224a4..8a639a1e81 100644 --- a/pkg/wallet/src/confirmation/overlay.ts +++ b/pkg/wallet/src/confirmation/overlay.ts @@ -13,6 +13,7 @@ const PASSPORT_OVERLAY_CONTENTS_ID = 'passport-overlay-contents'; const PASSPORT_OVERLAY_CLOSE_ID = `${PASSPORT_OVERLAY_ID}-close`; const PASSPORT_OVERLAY_TRY_AGAIN_ID = `${PASSPORT_OVERLAY_ID}-try-again`; +/* eslint-disable max-len */ const CLOSE_BUTTON_SVG = ` getOverlay(getGenericContents()); */ export class ConfirmationOverlay { private disableGenericPopupOverlay: boolean; + private disableBlockedPopupOverlay: boolean; + private overlay: HTMLDivElement | undefined; + private isBlockedOverlay: boolean; + private tryAgainListener: (() => void) | undefined; + private onCloseListener: (() => void) | undefined; constructor(popupOverlayOptions: PopupOverlayOptions, isBlockedOverlay: boolean = false) { @@ -286,6 +292,7 @@ export class ConfirmationOverlay { } } + // eslint-disable-next-line class-methods-use-this update(tryAgainOnClick: () => void) { const tryAgainButton = document.getElementById(PASSPORT_OVERLAY_TRY_AGAIN_ID); if (tryAgainButton) { @@ -293,7 +300,7 @@ export class ConfirmationOverlay { Object.assign(document.createElement('button'), { innerHTML: getTryAgainButton().match(/]*>([\s\S]*?)<\/button>/)![1], onclick: tryAgainOnClick, - }) + }), ); } } @@ -305,4 +312,3 @@ export class ConfirmationOverlay { } } } - diff --git a/pkg/wallet/src/confirmation/popup.ts b/pkg/wallet/src/confirmation/popup.ts index 8e04b2aa22..c34b36711c 100644 --- a/pkg/wallet/src/confirmation/popup.ts +++ b/pkg/wallet/src/confirmation/popup.ts @@ -45,4 +45,3 @@ export const openPopupCenter = ({ newWindow.focus(); return newWindow; }; - diff --git a/pkg/wallet/src/eip6963.ts b/pkg/wallet/src/eip6963.ts index bb91dded82..9be998a0d1 100644 --- a/pkg/wallet/src/eip6963.ts +++ b/pkg/wallet/src/eip6963.ts @@ -30,28 +30,16 @@ export interface EIP6963AnnounceProviderEvent extends CustomEvent { - // eslint-disable-next-line @typescript-eslint/no-bitwise - const r = Math.floor(Math.random() * 16); - // eslint-disable-next-line @typescript-eslint/no-bitwise - const v = c === 'x' ? r : (r % 4) + 8; - return v.toString(16); - }); -} - /** * Passport provider info for EIP-6963 + * UUID is a constant that uniquely identifies Immutable Passport as a provider type */ export const passportProviderInfo: EIP6963ProviderInfo = { // eslint-disable-next-line max-len icon: 'data:image/svg+xml,', name: 'Immutable Passport', rdns: 'com.immutable.passport', - uuid: generateUUID(), + uuid: 'ca329358-25a0-4e81-b162-7a2455e97395', }; /** @@ -70,4 +58,4 @@ export function announceProvider(detail: EIP6963ProviderDetail): void { // Listen for requests and re-announce const handler = () => window.dispatchEvent(event); window.addEventListener('eip6963:requestProvider', handler); -} \ No newline at end of file +} diff --git a/pkg/wallet/src/ejection.ts b/pkg/wallet/src/ejection.ts index 38abbbbf76..d8756be428 100644 --- a/pkg/wallet/src/ejection.ts +++ b/pkg/wallet/src/ejection.ts @@ -32,21 +32,21 @@ export async function prepareAndSignEjectionTransaction({ if (!transactionRequest.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a "to" field' + 'im_signEjectionTransaction requires a "to" field', ); } if (typeof transactionRequest.nonce === 'undefined') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a "nonce" field' + 'im_signEjectionTransaction requires a "nonce" field', ); } if (!transactionRequest.chainId) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a "chainId" field' + 'im_signEjectionTransaction requires a "chainId" field', ); } @@ -63,7 +63,7 @@ export async function prepareAndSignEjectionTransaction({ nonce, chainIdBigInt, zkEvmAddress, - ethSigner + ethSigner, ); return { @@ -71,4 +71,4 @@ export async function prepareAndSignEjectionTransaction({ data: signedTransaction, chainId: `eip155:${chainId}`, }; -} \ No newline at end of file +} diff --git a/pkg/wallet/src/guardian.ts b/pkg/wallet/src/guardian.ts index 6c35ace826..62bd2ecba9 100644 --- a/pkg/wallet/src/guardian.ts +++ b/pkg/wallet/src/guardian.ts @@ -6,7 +6,7 @@ import type { User } from '@imtbl/auth'; import { JsonRpcError, RpcErrorCode } from './errors'; import { getEip155ChainId } from './utils/chain'; -import type { TypedDataPayload } from './relayer'; +import type { TypedDataPayload } from './types'; import { ConfirmationScreen } from './confirmation/confirmation'; import { authenticatedFetch } from './utils/http-client'; @@ -14,6 +14,8 @@ export interface GuardianClientConfig { guardianUrl: string; /** Confirmation screen for showing confirmation UI */ confirmationScreen: ConfirmationScreen; + /** Enable cross-SDK bridge mode - throws errors instead of showing confirmation popups */ + crossSdkBridgeEnabled: boolean; } /** @@ -58,27 +60,35 @@ export class GuardianClient { payload, }, token: user.access_token, - } + }, ); - } catch (error: any) { + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); throw new JsonRpcError( RpcErrorCode.INTERNAL_ERROR, - `Message failed to validate: ${error.message}` + `Message failed to validate: ${errorMessage}`, ); } // Handle confirmation if required if (data.confirmationRequired && data.messageId) { + if (this.config.crossSdkBridgeEnabled) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Transaction rejected - confirmation required but cross-SDK bridge mode is enabled', + ); + } + const confirmed = await this.handleMessageConfirmation( data.messageId, walletAddress, - 'erc191' + 'erc191', ); if (!confirmed) { throw new JsonRpcError( RpcErrorCode.TRANSACTION_REJECTED, - 'Signature rejected by user' + 'Signature rejected by user', ); } } @@ -87,7 +97,12 @@ export class GuardianClient { /** * Evaluates an EIP-712 message */ - async evaluateEIP712Message(payload: TypedDataPayload, walletAddress: string, chainId: number, user: User): Promise { + async evaluateEIP712Message( + payload: TypedDataPayload, + walletAddress: string, + chainId: number, + user: User, + ): Promise { // User is guaranteed to be authenticated when this is called // (ensured by ensureAuthenticated() in provider) // Trust provider - use access_token directly @@ -103,27 +118,35 @@ export class GuardianClient { payload, }, token: user.access_token, - } + }, ); - } catch (error: any) { + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); throw new JsonRpcError( RpcErrorCode.INTERNAL_ERROR, - `Message failed to validate: ${error.message}` + `Message failed to validate: ${errorMessage}`, ); } // Handle confirmation if required if (data.confirmationRequired && data.messageId) { + if (this.config.crossSdkBridgeEnabled) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Transaction rejected - confirmation required but cross-SDK bridge mode is enabled', + ); + } + const confirmed = await this.handleMessageConfirmation( data.messageId, walletAddress, - 'eip712' + 'eip712', ); if (!confirmed) { throw new JsonRpcError( RpcErrorCode.TRANSACTION_REJECTED, - 'Signature rejected by user' + 'Signature rejected by user', ); } } @@ -135,12 +158,12 @@ export class GuardianClient { private async handleMessageConfirmation( messageId: string, walletAddress: string, - messageType: 'erc191' | 'eip712' + messageType: 'erc191' | 'eip712', ): Promise { const result = await this.config.confirmationScreen.requestMessageConfirmation( messageId, walletAddress, - messageType + messageType, ); return result.confirmed; } @@ -151,12 +174,12 @@ export class GuardianClient { private async handleTransactionConfirmation( transactionId: string, walletAddress: string, - chainId: number + chainId: number, ): Promise { const result = await this.config.confirmationScreen.requestConfirmation( transactionId, walletAddress, - getEip155ChainId(chainId) + getEip155ChainId(chainId), ); return result.confirmed; } @@ -165,6 +188,7 @@ export class GuardianClient { * Maps meta-transactions to Guardian API format * Guardian-specific transformation logic (converts bigint to string for JSON) */ + // eslint-disable-next-line class-methods-use-this private mapMetaTransactionsForGuardian( metaTransactions: Array<{ target: `0x${string}`; @@ -173,15 +197,15 @@ export class GuardianClient { gasLimit: bigint; delegateCall: boolean; revertOnError: boolean; - }> + }>, ): Array<{ - delegateCall: boolean; - revertOnError: boolean; - gasLimit: string; - target: string; - value: string; - data: string; - }> { + delegateCall: boolean; + revertOnError: boolean; + gasLimit: string; + target: string; + value: string; + data: string; + }> { return metaTransactions.map((tx) => ({ delegateCall: tx.delegateCall, revertOnError: tx.revertOnError, @@ -240,27 +264,35 @@ export class GuardianClient { }, }, token: user.access_token, - } + }, ); - } catch (error: any) { + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); throw new JsonRpcError( RpcErrorCode.INTERNAL_ERROR, - `Transaction failed to validate: ${error.message}` + `Transaction failed to validate: ${errorMessage}`, ); } // Handle confirmation if required if (data.confirmationRequired && data.transactionId) { + if (this.config.crossSdkBridgeEnabled) { + throw new JsonRpcError( + RpcErrorCode.TRANSACTION_REJECTED, + 'Transaction rejected - confirmation required but cross-SDK bridge mode is enabled', + ); + } + const confirmed = await this.handleTransactionConfirmation( data.transactionId, walletAddress, - parseInt(chainId.split(':')[1] || chainId) + parseInt(chainId.split(':')[1] || chainId, 10), ); if (!confirmed) { throw new JsonRpcError( RpcErrorCode.TRANSACTION_REJECTED, - 'Transaction rejected by user' + 'Transaction rejected by user', ); } } else if (!isBackgroundTransaction) { diff --git a/pkg/wallet/src/index.ts b/pkg/wallet/src/index.ts index 1ff22de4d7..249b2eb048 100644 --- a/pkg/wallet/src/index.ts +++ b/pkg/wallet/src/index.ts @@ -1,6 +1,9 @@ import type { Auth } from '@imtbl/auth'; import type { Provider, ChainConfig } from './provider'; +import { PassportEVMProvider } from './provider'; +import { announceProvider, passportProviderInfo } from './eip6963'; + export interface WalletConfig { /** Chain configurations - if omitted, uses default Immutable chains */ chains?: ChainConfig[]; @@ -15,15 +18,33 @@ export interface WalletConfig { }; /** Announce provider via EIP-6963 (default: true) */ announceProvider?: boolean; + /** Enable cross-SDK bridge mode - skips confirmation popups, throws errors instead (default: false) */ + crossSdkBridgeEnabled?: boolean; } export type { Signer } from './signer/signer'; -import { PassportEVMProvider } from './provider'; -import { announceProvider, passportProviderInfo } from './eip6963'; +function getDefaultChains(): ChainConfig[] { + return [ + { + chainId: 13473, + rpcUrl: 'https://rpc.testnet.immutable.com', + relayerUrl: 'https://api.sandbox.immutable.com/relayer-mr', + apiUrl: 'https://api.sandbox.immutable.com', + name: 'Immutable zkEVM Testnet', + }, + { + chainId: 13371, + rpcUrl: 'https://rpc.immutable.com', + relayerUrl: 'https://api.immutable.com/relayer-mr', + apiUrl: 'https://api.immutable.com', + name: 'Immutable zkEVM', + }, + ]; +} export async function connectWallet( - config?: WalletConfig + config?: WalletConfig, ): Promise { const chains = config?.chains || getDefaultChains(); @@ -33,6 +54,7 @@ export async function connectWallet( authenticatedUser: undefined, auth: config?.auth, popupOverlayOptions: config?.popupOverlayOptions, + crossSdkBridgeEnabled: config?.crossSdkBridgeEnabled || false, }); if (config?.announceProvider !== false) { @@ -44,23 +66,3 @@ export async function connectWallet( return provider; } - -function getDefaultChains(): ChainConfig[] { - return [ - { - chainId: 13473, - rpcUrl: 'https://rpc.testnet.immutable.com', - relayerUrl: 'https://api.sandbox.immutable.com/relayer-mr', - apiUrl: 'https://api.sandbox.immutable.com', - name: 'Immutable zkEVM Testnet', - }, - { - chainId: 13371, - rpcUrl: 'https://rpc.immutable.com', - relayerUrl: 'https://api.immutable.com/relayer-mr', - apiUrl: 'https://api.immutable.com', - name: 'Immutable zkEVM', - }, - ]; -} - diff --git a/pkg/wallet/src/metatransaction.ts b/pkg/wallet/src/metatransaction.ts index c78a0775a1..d0868ec11a 100644 --- a/pkg/wallet/src/metatransaction.ts +++ b/pkg/wallet/src/metatransaction.ts @@ -1,4 +1,3 @@ - import type { User } from '@imtbl/auth'; import { toHex, createPublicClient, http } from 'viem'; import { JsonRpcError, RpcErrorCode } from './errors'; @@ -27,7 +26,7 @@ export interface TransactionRequest { } export function buildMetaTransaction( - request: TransactionRequest + request: TransactionRequest, ): MetaTransaction { if (!request.to) { throw new Error('TransactionRequest.to is required'); @@ -45,7 +44,6 @@ export function buildMetaTransaction( }; } - /** * Gets nonce from smart contract wallet via RPC * Encodes nonce with space: space in upper 160 bits, nonce in lower 96 bits @@ -54,7 +52,7 @@ export function buildMetaTransaction( export async function getNonce( rpcUrl: string, smartContractWalletAddress: string, - nonceSpace?: bigint + nonceSpace?: bigint, ): Promise { const space = nonceSpace || BigInt(0); const functionSelector = getFunctionSelector('readNonce(uint256)'); @@ -77,15 +75,22 @@ export async function getNonce( } return BigInt(0); - } catch (error: any) { - if (error?.message?.includes('BAD_DATA') || error?.message?.includes('execution reverted')) { + } catch (error: unknown) { + // If wallet is not deployed, RPC call will fail + // Return 0 nonce for undeployed wallets + const errorMessage = error instanceof Error ? error.message : String(error); + if ( + errorMessage.includes('BAD_DATA') + || errorMessage.includes('execution reverted') + || errorMessage.includes('revert') + || errorMessage.includes('invalid opcode') + ) { return BigInt(0); } throw error; } } - /** * Builds meta-transactions array with fee transaction * Fetches nonce and fee option in parallel, then builds final transaction array @@ -97,12 +102,12 @@ export async function buildMetaTransactions( zkevmAddress: string, chainId: number, user: User, - nonceSpace?: bigint + nonceSpace?: bigint, ): Promise<{ transactions: [MetaTransaction, ...MetaTransaction[]]; nonce: bigint }> { if (!transactionRequest.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'eth_sendTransaction requires a "to" field' + 'eth_sendTransaction requires a "to" field', ); } @@ -144,7 +149,7 @@ export async function validateAndSignTransaction( signer: Signer, guardianClient: GuardianClient, user: User, - isBackgroundTransaction: boolean = false + isBackgroundTransaction: boolean = false, ): Promise { const [, signedTransactionData] = await Promise.all([ guardianClient.validateEVMTransaction({ @@ -160,12 +165,9 @@ export async function validateAndSignTransaction( nonce, chainId, walletAddress, - signer + signer, ), ]); return signedTransactionData; } - - - diff --git a/pkg/wallet/src/provider.ts b/pkg/wallet/src/provider.ts index 05e227f811..73fe50b7c9 100644 --- a/pkg/wallet/src/provider.ts +++ b/pkg/wallet/src/provider.ts @@ -3,8 +3,10 @@ */ import type { User, Auth } from '@imtbl/auth'; +import { + toHex, fromHex, createPublicClient, http, isAddress, hexToString, +} from 'viem'; import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from './errors'; -import { toHex, fromHex, createPublicClient, http, isAddress } from 'viem'; import { getFunctionSelector } from './utils/abi'; import type { Signer } from './signer/signer'; import { MagicTEESigner } from './signer/magic'; @@ -12,21 +14,12 @@ import { RelayerClient } from './relayer'; import { GuardianClient } from './guardian'; import { ApiClient } from './api'; import { ConfirmationScreen } from './confirmation/confirmation'; -import { hexToString } from './utils/hex'; import { packSignatures } from './sequence'; import { signERC191Message, signTypedData } from './signer/signing'; import { buildMetaTransactions, validateAndSignTransaction } from './metatransaction'; import { prepareAndSignEjectionTransaction } from './ejection'; import type { TransactionRequest } from './metatransaction'; - -function getPassportDomain(chains: ChainConfig[]): string { - const isSandbox = chains.some(chain => - chain.apiUrl.includes('sandbox') || chain.apiUrl.includes('testnet') - ); - return isSandbox - ? 'https://passport.sandbox.immutable.com' - : 'https://passport.immutable.com'; -} +import type { TypedDataPayload } from './types'; /** * Simple event emitter for provider events @@ -46,7 +39,7 @@ class SimpleEventEmitter { } emit(event: string, ...args: any[]): void { - this.listeners.get(event)?.forEach(listener => listener(...args)); + this.listeners.get(event)?.forEach((listener) => listener(...args)); } } @@ -83,6 +76,8 @@ export interface ProviderConfig { disableGenericPopupOverlay?: boolean; disableBlockedPopupOverlay?: boolean; }; + /** Enable cross-SDK bridge mode - skips confirmation popups, throws errors instead */ + crossSdkBridgeEnabled?: boolean; } /** @@ -94,17 +89,17 @@ export interface Provider { * @see https://eips.ethereum.org/EIPS/eip-1193 */ request(args: RequestArguments): Promise; - + /** * Subscribe to provider events */ on(event: string, listener: (...args: any[]) => void): void; - + /** * Unsubscribe from provider events */ removeListener(event: string, listener: (...args: any[]) => void): void; - + /** * Indicates this is a Passport provider */ @@ -116,7 +111,7 @@ export interface Provider { */ export interface RequestArguments { method: string; - params?: Array; + params?: unknown[]; } /** @@ -133,19 +128,34 @@ export class PassportEVMProvider implements Provider { public readonly isPassport: boolean = true; private chains: Map; + private currentChainId: number; + private currentRpcUrl: string; + private eventEmitter: SimpleEventEmitter; + private authenticatedUser?: User; + private auth?: Auth; + private signer?: Signer; + private walletAddress?: string; + private passportDomain: string; + + private crossSdkBridgeEnabled: boolean; + // Clients are always initialized in constructor via initializeClients() private relayerClient!: RelayerClient; + private guardianClient!: GuardianClient; + private apiClient!: ApiClient; + private confirmationScreen: ConfirmationScreen; + // Cached HTTP transport for RPC calls (recreated when chain switches) private rpcTransport = http(''); @@ -171,28 +181,30 @@ export class PassportEVMProvider implements Provider { this.authenticatedUser = config.authenticatedUser; this.auth = config.auth; this.eventEmitter = new SimpleEventEmitter(); - + // Initialize RPC transport this.rpcTransport = http(this.currentRpcUrl); - + // Determine passport domain from chain config - this.passportDomain = getPassportDomain(config.chains); - + this.passportDomain = 'https://passport.immutable.com'; + this.crossSdkBridgeEnabled = config.crossSdkBridgeEnabled || false; + // Initialize confirmation screen this.confirmationScreen = new ConfirmationScreen({ passportDomain: this.passportDomain, popupOverlayOptions: config.popupOverlayOptions, + crossSdkBridgeEnabled: this.crossSdkBridgeEnabled, }); - + // Initialize clients eagerly (they can exist without authenticated user) // Clients are stateless - user is passed as parameter to methods - this.initializeClients(); - + this.initializeClients(config.crossSdkBridgeEnabled || false); + // If auth client provided, set it up (will try to get existing user) if (this.auth) { this.setAuth(this.auth); } - + // If authenticated user provided, set it up if (this.authenticatedUser) { this.setAuthenticatedUser(this.authenticatedUser); @@ -203,7 +215,7 @@ export class PassportEVMProvider implements Provider { * Initializes or updates API clients * Clients can be created without authenticated user - they'll fail on requests until user is set */ - private initializeClients(): void { + private initializeClients(crossSdkBridgeEnabled: boolean): void { const chainConfig = this.chains.get(this.currentChainId)!; this.relayerClient = new RelayerClient({ @@ -213,6 +225,7 @@ export class PassportEVMProvider implements Provider { this.guardianClient = new GuardianClient({ guardianUrl: chainConfig.apiUrl, confirmationScreen: this.confirmationScreen, + crossSdkBridgeEnabled, }); this.apiClient = new ApiClient({ @@ -227,7 +240,7 @@ export class PassportEVMProvider implements Provider { */ private setAuthenticatedUser(user: User): void { this.authenticatedUser = user; - this.initializeClients(); + this.initializeClients(this.crossSdkBridgeEnabled); this.initializeSigner(); } @@ -244,21 +257,18 @@ export class PassportEVMProvider implements Provider { const magicTeeBasePath = isSandbox ? 'https://api.sandbox.immutable.com/magic-tee' : 'https://api.immutable.com/magic-tee'; - - // Magic config values - these should be consistent across environments - // TODO: These might need to come from config or environment - const magicPublishableApiKey = process.env.MAGIC_PUBLISHABLE_API_KEY || ''; - const magicProviderId = process.env.MAGIC_PROVIDER_ID || 'immutable'; - - // Only initialize if we have the required config - if (magicPublishableApiKey && magicProviderId) { - this.signer = new MagicTEESigner({ - magicTeeBasePath, - magicPublishableApiKey, - magicProviderId, - authenticatedUser: this.authenticatedUser, - }); - } + + // Magic config values - hardcoded (same for production and sandbox) + // These match the values from the original Passport SDK + const magicPublishableApiKey = 'pk_live_10F423798A540ED7'; + const magicProviderId = 'aa80b860-8869-4f13-9000-6a6ad3d20017'; + + this.signer = new MagicTEESigner({ + magicTeeBasePath, + magicPublishableApiKey, + magicProviderId, + authenticatedUser: this.authenticatedUser, + }); } /** @@ -299,20 +309,22 @@ export class PassportEVMProvider implements Provider { if (!this.chains.has(chainId)) { throw new JsonRpcError( ProviderErrorCode.UNSUPPORTED_METHOD, - `Chain ${chainId} not supported` + `Chain ${chainId} not supported`, ); } this.currentChainId = chainId; const chainConfig = this.chains.get(chainId)!; - + this.currentRpcUrl = chainConfig.rpcUrl; - + // Recreate RPC transport for new chain this.rpcTransport = http(this.currentRpcUrl); // Reinitialize clients with new chain config (always, regardless of user) - this.initializeClients(); + // Note: crossSdkBridgeEnabled is stored in confirmationScreen config, so we need to read it back + // For simplicity, we'll store it as a private field + this.initializeClients(this.crossSdkBridgeEnabled); // Emit chainChanged event this.eventEmitter.emit('chainChanged', toHex(chainId)); @@ -326,21 +338,11 @@ export class PassportEVMProvider implements Provider { } /** - * Gets the wallet address from authenticated user + * Gets the cached wallet address + * Returns undefined if wallet not yet registered (call eth_requestAccounts to register) */ private async getWalletAddress(): Promise { - if (this.walletAddress) { - return this.walletAddress; - } - - if (!this.authenticatedUser) { - return undefined; - } - - // Try to extract from user profile (if already registered) - // The profile might contain zkEVM address in custom claims - // For now, return undefined - will be set during registration - return undefined; + return this.walletAddress; } /** @@ -351,7 +353,7 @@ export class PassportEVMProvider implements Provider { if (!address) { throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, - 'Unauthorised - call eth_requestAccounts first' + 'Unauthorised - call eth_requestAccounts first', ); } return address; @@ -370,7 +372,7 @@ export class PassportEVMProvider implements Provider { if (!this.signer) { throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, - 'Signer not available. User must be authenticated for signing operations.' + 'Signer not available. User must be authenticated for signing operations.', ); } return this.signer; @@ -394,11 +396,10 @@ export class PassportEVMProvider implements Provider { throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, - 'User not authenticated. Please provide an auth client or login first.' + 'User not authenticated. Please provide an auth client or login first.', ); } - /** * Handles eth_requestAccounts * Automatically triggers login if auth client is provided and user not authenticated @@ -433,7 +434,7 @@ export class PassportEVMProvider implements Provider { chainName, ethereumAddress, ethereumSignature, - this.authenticatedUser! + this.authenticatedUser!, ); this.walletAddress = counterfactualAddress; @@ -454,14 +455,14 @@ export class PassportEVMProvider implements Provider { if (!transactionRequest?.to || typeof transactionRequest.to !== 'string') { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'Transaction must include to field' + 'Transaction must include to field', ); } if (!isAddress(transactionRequest.to)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - `Invalid address: ${transactionRequest.to}` + `Invalid address: ${transactionRequest.to}`, ); } @@ -476,7 +477,7 @@ export class PassportEVMProvider implements Provider { this.relayerClient, address, this.currentChainId, - this.authenticatedUser! + this.authenticatedUser!, ); const chainId = BigInt(this.currentChainId); @@ -490,7 +491,7 @@ export class PassportEVMProvider implements Provider { signer, this.guardianClient, this.authenticatedUser!, - false + false, ); // Send to relayer @@ -498,7 +499,7 @@ export class PassportEVMProvider implements Provider { address, // to is the wallet address signedTransactionData, this.currentChainId, - this.authenticatedUser! + this.authenticatedUser!, ); // Poll for transaction hash @@ -507,38 +508,45 @@ export class PassportEVMProvider implements Provider { /** * Polls relayer for transaction hash + * authenticatedUser is guaranteed by callers (ensureAuthenticated called before this) */ private async pollTransaction(relayerId: string): Promise { - if (!this.authenticatedUser) { - throw new JsonRpcError( - ProviderErrorCode.UNAUTHORIZED, - 'User not authenticated' - ); - } - const maxAttempts = 30; const delayMs = 1000; + // Polling loop - await is intentional + // eslint-disable-next-line no-await-in-loop for (let i = 0; i < maxAttempts; i++) { - const tx = await this.relayerClient.imGetTransactionByHash(relayerId, this.authenticatedUser); + // authenticatedUser is guaranteed by callers (ensureAuthenticated called before this) + // eslint-disable-next-line no-await-in-loop + const tx = await this.relayerClient.imGetTransactionByHash(relayerId, this.authenticatedUser!); if (tx.status === 'SUCCESSFUL' || tx.status === 'SUBMITTED') { + if (!tx.hash) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + 'Transaction hash not available', + ); + } return tx.hash; } if (tx.status === 'REVERTED' || tx.status === 'FAILED') { throw new JsonRpcError( RpcErrorCode.TRANSACTION_REJECTED, - tx.statusMessage || 'Transaction failed' + tx.statusMessage || 'Transaction failed', ); } - await new Promise(resolve => setTimeout(resolve, delayMs)); + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(() => resolve(), delayMs); + }); } throw new JsonRpcError( RpcErrorCode.RPC_SERVER_ERROR, - 'Transaction polling timeout' + 'Transaction polling timeout', ); } @@ -556,7 +564,7 @@ export class PassportEVMProvider implements Provider { if (!fromAddress || !message) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'personal_sign requires an address and a message' + 'personal_sign requires an address and a message', ); } @@ -564,20 +572,19 @@ export class PassportEVMProvider implements Provider { if (!isAddress(fromAddress)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - `Invalid address: ${fromAddress}` + `Invalid address: ${fromAddress}`, ); } if (fromAddress.toLowerCase() !== address.toLowerCase()) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'personal_sign requires the signer to be the from address' + 'personal_sign requires the signer to be the from address', ); } - // Convert hex to string if needed - const payload = hexToString(message); + const payload = hexToString(message as `0x${string}`); const chainId = BigInt(this.currentChainId); // Evaluate with guardian (passes wallet address for confirmation) @@ -614,7 +621,7 @@ export class PassportEVMProvider implements Provider { this.relayerClient, address, this.currentChainId, - this.authenticatedUser! + this.authenticatedUser!, ); const chainId = BigInt(this.currentChainId); @@ -628,7 +635,7 @@ export class PassportEVMProvider implements Provider { signer, this.guardianClient, this.authenticatedUser!, - false + false, ); // Send to relayer @@ -636,9 +643,9 @@ export class PassportEVMProvider implements Provider { address, signedTransactionData, this.currentChainId, - this.authenticatedUser! + this.authenticatedUser!, ); - + // Wait for deployment to complete await this.pollTransaction(relayerId); } @@ -657,7 +664,7 @@ export class PassportEVMProvider implements Provider { if (!fromAddress || !typedDataParam) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'eth_signTypedData_v4 requires an address and typed data' + 'eth_signTypedData_v4 requires an address and typed data', ); } @@ -665,35 +672,38 @@ export class PassportEVMProvider implements Provider { if (!isAddress(fromAddress)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - `Invalid address: ${fromAddress}` + `Invalid address: ${fromAddress}`, ); } // Parse typed data - const typedData: any = typeof typedDataParam === 'string' - ? JSON.parse(typedDataParam) - : typedDataParam; + const typedData: TypedDataPayload = typeof typedDataParam === 'string' + ? JSON.parse(typedDataParam) as TypedDataPayload + : typedDataParam as TypedDataPayload; // Validate typed data structure if (!typedData.types || !typedData.domain || !typedData.primaryType || !typedData.message) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'Invalid typed data: missing required fields' + 'Invalid typed data: missing required fields', ); } // Validate chainId matches current chain if (typedData.domain.chainId) { - const providedChainId = typeof typedData.domain.chainId === 'string' - ? (typedData.domain.chainId.startsWith('0x') - ? parseInt(typedData.domain.chainId, 16) - : parseInt(typedData.domain.chainId, 10)) - : typedData.domain.chainId; + let providedChainId: number; + if (typeof typedData.domain.chainId === 'string') { + providedChainId = typedData.domain.chainId.startsWith('0x') + ? parseInt(typedData.domain.chainId, 16) + : parseInt(typedData.domain.chainId, 10); + } else { + providedChainId = typedData.domain.chainId; + } if (BigInt(providedChainId) !== BigInt(this.currentChainId)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - `Invalid chainId, expected ${this.currentChainId}` + `Invalid chainId, expected ${this.currentChainId}`, ); } } @@ -701,17 +711,27 @@ export class PassportEVMProvider implements Provider { const chainId = BigInt(this.currentChainId); // Evaluate with guardian (passes wallet address for confirmation) - await this.guardianClient.evaluateEIP712Message(typedData, address, this.currentChainId, this.authenticatedUser!); + await this.guardianClient.evaluateEIP712Message( + typedData, + address, + this.currentChainId, + this.authenticatedUser!, + ); // Get relayer signature - const relayerSignature = await this.relayerClient.imSignTypedData(address, typedData, this.currentChainId, this.authenticatedUser!); + const relayerSignature = await this.relayerClient.imSignTypedData( + address, + typedData, + this.currentChainId, + this.authenticatedUser!, + ); // If signer has signTypedData method, use it (ethers/viem signers) if (signer.signTypedData) { const eoaSignature = await signer.signTypedData( typedData.domain, typedData.types, - typedData.message + typedData.message, ); const eoaAddress = await signer.getAddress(); return packSignatures(eoaSignature, eoaAddress, relayerSignature); @@ -729,7 +749,7 @@ export class PassportEVMProvider implements Provider { if (!chainIdHex) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'chainId is required' + 'chainId is required', ); } @@ -749,7 +769,7 @@ export class PassportEVMProvider implements Provider { if (!chainParams?.chainId || !chainParams?.rpcUrls?.[0]) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'chainId and rpcUrls are required' + 'chainId and rpcUrls are required', ); } @@ -757,15 +777,19 @@ export class PassportEVMProvider implements Provider { ? Number(fromHex(chainParams.chainId as `0x${string}`, 'number')) : chainParams.chainId; - // Extract API URLs from chain params or use defaults - const apiUrl = chainParams.apiUrl || this.chains.get(this.currentChainId)?.apiUrl || ''; - const relayerUrl = chainParams.relayerUrl || this.chains.get(this.currentChainId)?.relayerUrl || ''; + // Require API URLs - don't use defaults from current chain (they may be wrong) + if (!chainParams.apiUrl || !chainParams.relayerUrl) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'apiUrl and relayerUrl are required when adding a new chain', + ); + } this.addChain({ chainId, rpcUrl: chainParams.rpcUrls[0], - relayerUrl, - apiUrl, + relayerUrl: chainParams.relayerUrl, + apiUrl: chainParams.apiUrl, name: chainParams.chainName || `Chain ${chainId}`, }); @@ -787,7 +811,7 @@ export class PassportEVMProvider implements Provider { if (!params || params.length !== 1) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a singular param (transaction)' + 'im_signEjectionTransaction requires a singular param (transaction)', ); } @@ -811,7 +835,7 @@ export class PassportEVMProvider implements Provider { if (!clientId) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - 'im_addSessionActivity requires a clientId' + 'im_addSessionActivity requires a clientId', ); } @@ -838,21 +862,25 @@ export class PassportEVMProvider implements Provider { return; } - const apiUrl = chainConfig.apiUrl; - const isSandbox = apiUrl.includes('sandbox') || apiUrl.includes('testnet'); - // Session activity always uses production API const sessionActivityUrl = 'https://api.immutable.com/v1/sdk/session-activity/check'; + const params = new URLSearchParams({ + clientId, + wallet: walletAddress, + checkCount: '0', + sendCount: '0', + }); + const url = `${sessionActivityUrl}?${params.toString()}`; try { const response = await fetch( - `${sessionActivityUrl}?clientId=${encodeURIComponent(clientId)}&wallet=${encodeURIComponent(walletAddress)}&checkCount=0&sendCount=0`, + url, { method: 'GET', headers: { - 'Authorization': `Bearer ${this.authenticatedUser.access_token}`, + Authorization: `Bearer ${this.authenticatedUser.access_token}`, }, - } + }, ); if (!response.ok) { @@ -863,7 +891,7 @@ export class PassportEVMProvider implements Provider { } const data = await response.json(); - + // If contract address and function name are provided, send a background transaction if (data.contractAddress && data.functionName) { // Send background transaction in nonce space 1 @@ -871,7 +899,7 @@ export class PassportEVMProvider implements Provider { walletAddress, data.contractAddress, data.functionName, - data.delay + data.delay, ); } } catch (error) { @@ -886,7 +914,7 @@ export class PassportEVMProvider implements Provider { walletAddress: string, contractAddress: string, functionName: string, - delay?: number + delay?: number, ): Promise { try { await this.ensureAuthenticated(); @@ -907,7 +935,7 @@ export class PassportEVMProvider implements Provider { walletAddress, this.currentChainId, this.authenticatedUser!, - nonceSpace + nonceSpace, ); const chainId = BigInt(this.currentChainId); @@ -921,7 +949,7 @@ export class PassportEVMProvider implements Provider { signer, this.guardianClient, this.authenticatedUser!, - true // isBackgroundTransaction + true, // isBackgroundTransaction ); // Send to relayer @@ -929,12 +957,14 @@ export class PassportEVMProvider implements Provider { walletAddress, signedTransactionData, this.currentChainId, - this.authenticatedUser! + this.authenticatedUser!, ); // Wait for delay if specified if (delay && delay > 0) { - await new Promise(resolve => setTimeout(resolve, delay * 1000)); + await new Promise((resolve) => { + setTimeout(() => resolve(), delay * 1000); + }); } } catch (error) { // Silently fail - background transaction is non-critical @@ -1004,7 +1034,7 @@ export class PassportEVMProvider implements Provider { default: throw new JsonRpcError( ProviderErrorCode.UNSUPPORTED_METHOD, - `Method ${request.method} not supported` + `Method ${request.method} not supported`, ); } } @@ -1017,7 +1047,7 @@ export class PassportEVMProvider implements Provider { default: { throw new JsonRpcError( ProviderErrorCode.UNSUPPORTED_METHOD, - `Method ${request.method} not supported` + `Method ${request.method} not supported`, ); } } @@ -1026,7 +1056,7 @@ export class PassportEVMProvider implements Provider { /** * EIP-1193 request method */ - public async request(request: RequestArguments): Promise { + public async request(request: RequestArguments): Promise { try { return await this.performRequest(request); } catch (error) { diff --git a/pkg/wallet/src/relayer.ts b/pkg/wallet/src/relayer.ts index 0e9551f356..486a9cc160 100644 --- a/pkg/wallet/src/relayer.ts +++ b/pkg/wallet/src/relayer.ts @@ -3,26 +3,11 @@ */ import type { User } from '@imtbl/auth'; +import { encodeAbiParameters } from 'viem'; import { getEip155ChainId } from './utils/chain'; import { jsonRpcRequest } from './utils/http-client'; -import { encodeAbiParameters } from 'viem'; import type { MetaTransaction } from './metatransaction'; - -export interface TypedDataPayload { - types: { - EIP712Domain: Array<{ name: string; type: string }>; - [key: string]: Array<{ name: string; type: string }>; - }; - domain: { - name?: string; - version?: string; - chainId?: number | string; - verifyingContract?: string; - salt?: string; - }; - primaryType: string; - message: Record; -} +import type { TypedDataPayload } from './types'; export interface RelayerClientConfig { relayerUrl: string; @@ -52,7 +37,7 @@ export class RelayerClient { /** * Makes a request to the relayer API */ - private async request(method: string, params: any[], user: User): Promise { + private async request(method: string, params: unknown[], user: User): Promise { // User is guaranteed to be authenticated when this is called // (ensured by ensureAuthenticated() in provider) // Keep defensive check here as final safety net @@ -60,11 +45,11 @@ export class RelayerClient { throw new Error('User not authenticated'); } - return jsonRpcRequest( + return jsonRpcRequest( `${this.config.relayerUrl}/v1/transactions`, method, params, - user.access_token + user.access_token, ); } @@ -72,7 +57,7 @@ export class RelayerClient { * Sends a transaction via relayer */ async ethSendTransaction(to: string, data: string, chainId: number, user: User): Promise { - return this.request('eth_sendTransaction', [{ + return this.request('eth_sendTransaction', [{ to, data, chainId: getEip155ChainId(chainId), @@ -82,15 +67,23 @@ export class RelayerClient { /** * Gets transaction by hash */ - async imGetTransactionByHash(hash: string, user: User): Promise { - return this.request('im_getTransactionByHash', [hash], user); + async imGetTransactionByHash(hash: string, user: User): Promise<{ + status: string; + hash?: string; + statusMessage?: string; + }> { + return this.request<{ + status: string; + hash?: string; + statusMessage?: string; + }>('im_getTransactionByHash', [hash], user); } /** * Signs a message via relayer */ async imSign(address: string, message: string, chainId: number, user: User): Promise { - return this.request('im_sign', [{ + return this.request('im_sign', [{ chainId: getEip155ChainId(chainId), address, message, @@ -101,7 +94,7 @@ export class RelayerClient { * Signs typed data via relayer */ async imSignTypedData(address: string, payload: TypedDataPayload, chainId: number, user: User): Promise { - return this.request('im_signTypedData', [{ + return this.request('im_signTypedData', [{ chainId: getEip155ChainId(chainId), address, eip712Payload: payload, @@ -111,8 +104,13 @@ export class RelayerClient { /** * Gets fee options for a transaction */ - async imGetFeeOptions(userAddress: string, data: string, chainId: number, user: User): Promise { - return this.request('im_getFeeOptions', [{ + async imGetFeeOptions( + userAddress: string, + data: string, + chainId: number, + user: User, + ): Promise { + return this.request('im_getFeeOptions', [{ userAddress, data, chainId: getEip155ChainId(chainId), @@ -127,7 +125,7 @@ export class RelayerClient { walletAddress: string, transactions: MetaTransaction[], chainId: number, - user: User + user: User, ): Promise { const META_TRANSACTIONS_TYPE = `tuple( bool delegateCall, @@ -140,7 +138,7 @@ export class RelayerClient { const encodedTransactions = encodeAbiParameters( [{ type: META_TRANSACTIONS_TYPE }], - [transactions] + [transactions], ); const feeOptions = await this.imGetFeeOptions(walletAddress, encodedTransactions, chainId, user); @@ -150,9 +148,9 @@ export class RelayerClient { } const imxFeeOption = feeOptions.find( - (feeOption) => feeOption.tokenSymbol === 'IMX' + (feeOption) => feeOption.tokenSymbol === 'IMX', ); - + if (!imxFeeOption) { throw new Error('Failed to retrieve fees for IMX token'); } @@ -160,4 +158,3 @@ export class RelayerClient { return imxFeeOption; } } - diff --git a/pkg/wallet/src/sequence.ts b/pkg/wallet/src/sequence.ts index 78b12592d0..1ededb6669 100644 --- a/pkg/wallet/src/sequence.ts +++ b/pkg/wallet/src/sequence.ts @@ -3,11 +3,13 @@ * Minimal implementation for Immutable wallet signature format */ -import { isAddress, keccak256, encodeAbiParameters, fromHex } from 'viem'; +import { + isAddress, keccak256, encodeAbiParameters, fromHex, +} from 'viem'; import { cleanSignature, cleanAddress, removeHexPrefix } from './utils/hex'; import { JsonRpcError, RpcErrorCode } from './errors'; import type { Signer } from './signer/signer'; -import { encodeMessageSubDigest } from './signer/signing'; +import { encodeMessageSubDigest } from './utils/subdigest'; import { getFunctionSelector } from './utils/abi'; import type { MetaTransaction } from './metatransaction'; @@ -69,8 +71,10 @@ export function encodeSignature(options: EncodeSignatureOptions): string { // bits 2-7: weight (0-63) const isDynamic = signer.isDynamic ? 1 : 0; const unrecovered = signer.unrecovered !== false ? 1 : 0; // Default to true + // eslint-disable-next-line no-bitwise const weight = signer.weight & 0x3f; // Mask to 6 bits - + + // eslint-disable-next-line no-bitwise const flagsByte = (isDynamic | (unrecovered << 1) | (weight << 2)).toString(16).padStart(2, '0'); encoded += flagsByte; @@ -96,19 +100,23 @@ export function encodeSignature(options: EncodeSignatureOptions): string { */ export function decodeSignature(signature: string): DecodedSignature { const hex = removeHexPrefix(signature); - + if (hex.length < 8) { throw new Error('Invalid signature: too short'); } // Read version (1 byte) - const version = parseInt(hex.slice(0, 2), 16); + const versionHex = hex.slice(0, 2); + const version = parseInt( + versionHex, + 16, + ); // Read threshold (1 byte) const threshold = parseInt(hex.slice(2, 4), 16); // Skip reserved byte (offset 4-6) - + // Read signers count (1 byte) const signersCount = parseInt(hex.slice(6, 8), 16); @@ -124,8 +132,11 @@ export function decodeSignature(signature: string): DecodedSignature { const flagsByte = parseInt(hex.slice(offset, offset + 2), 16); offset += 2; + // eslint-disable-next-line no-bitwise const isDynamic = (flagsByte & 0x01) !== 0; + // eslint-disable-next-line no-bitwise const unrecovered = (flagsByte & 0x02) !== 0; + // eslint-disable-next-line no-bitwise const weight = (flagsByte >> 2) & 0x3f; // Read signature (65 bytes = 130 hex chars) @@ -166,21 +177,21 @@ export function decodeSignature(signature: string): DecodedSignature { export function packSignatures( eoaSignature: string, eoaAddress: string, - relayerSignature: string + relayerSignature: string, ): string { // Validate address format if (!isAddress(eoaAddress)) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, - `Invalid address: ${eoaAddress}` + `Invalid address: ${eoaAddress}`, ); } // Decode relayer signature (add 0x prefix if missing, and version/threshold prefix) - const relayerSigWithPrefix = relayerSignature.startsWith('0x') - ? relayerSignature + const relayerSigWithPrefix = relayerSignature.startsWith('0x') + ? relayerSignature : `0x0000${relayerSignature}`; // Add version/threshold prefix for decoding - + const decoded = decodeSignature(relayerSigWithPrefix); const relayerSigners = decoded.signers; @@ -222,7 +233,7 @@ export async function signMetaTransactions( nonce: bigint, chainId: bigint, walletAddress: string, - signer: Signer + signer: Signer, ): Promise { // Get digest of transactions and nonce const META_TRANSACTIONS_TYPE = `tuple( @@ -239,11 +250,11 @@ export async function signMetaTransactions( { type: 'uint256' }, { type: META_TRANSACTIONS_TYPE }, ], - [nonce, metaTransactions] as readonly [bigint, readonly MetaTransaction[]] + [nonce, metaTransactions] as readonly [bigint, readonly MetaTransaction[]], ); - + const digest = keccak256(packMetaTransactionsNonceData); - + // Create sub-digest with chain ID and wallet address const completePayload = encodeMessageSubDigest(chainId, walletAddress, digest); const hash = keccak256(`0x${Buffer.from(completePayload, 'utf8').toString('hex')}` as `0x${string}`); @@ -274,21 +285,23 @@ export async function signMetaTransactions( // Encode parameters const encodedParams = encodeAbiParameters( [ - { type: 'tuple[]', components: [ - { name: 'delegateCall', type: 'bool' }, - { name: 'revertOnError', type: 'bool' }, - { name: 'gasLimit', type: 'uint256' }, - { name: 'target', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ]}, + { + type: 'tuple[]', + components: [ + { name: 'delegateCall', type: 'bool' }, + { name: 'revertOnError', type: 'bool' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'target', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + ], + }, { type: 'uint256' }, { type: 'bytes' }, ], - [metaTransactions as any, nonce, encodedSignature as `0x${string}`] + [metaTransactions as any, nonce, encodedSignature as `0x${string}`], ); // Prepend function selector return executeSelector + encodedParams.slice(2); } - diff --git a/pkg/wallet/src/signer/signing.ts b/pkg/wallet/src/signer/signing.ts index 19d72a8e81..cf84c15cde 100644 --- a/pkg/wallet/src/signer/signing.ts +++ b/pkg/wallet/src/signer/signing.ts @@ -1,8 +1,10 @@ -import { hashTypedData, hashMessage, keccak256, hexToBytes } from 'viem'; -import type { TypedDataPayload } from '../relayer'; +import { + hashTypedData, hashMessage, keccak256, hexToBytes, +} from 'viem'; +import type { TypedDataPayload } from '../types'; import { packSignatures } from '../sequence'; import type { Signer } from './signer'; -import { removeHexPrefix } from '../utils/hex'; +import { encodeMessageSubDigest } from '../utils/subdigest'; /** * Signs ERC-191 message with Immutable wallet sub-digest @@ -12,7 +14,7 @@ export async function signERC191Message( chainId: bigint, payload: string, signer: Signer, - walletAddress: string + walletAddress: string, ): Promise { const digest = hashMessage(payload); const subDigest = encodeMessageSubDigest(chainId, walletAddress, digest); @@ -31,7 +33,7 @@ export async function signTypedData( relayerSignature: string, chainId: bigint, walletAddress: string, - signer: Signer + signer: Signer, ): Promise { const { EIP712Domain, ...types } = typedData.types; @@ -58,20 +60,3 @@ export async function signTypedData( return packSignatures(eoaSignature, eoaAddress, relayerSignature); } - -/** - * Encodes message sub-digest for Immutable wallet contract authentication - * Format: \x19\x01{chainId}{walletAddress}{digest} - */ -export function encodeMessageSubDigest( - chainId: bigint, - walletAddress: string, - digest: string -): string { - const prefix = '\x19\x01'; - const chainIdHex = chainId.toString(16).padStart(64, '0'); - const address = removeHexPrefix(walletAddress).toLowerCase(); - const digestHex = removeHexPrefix(digest); - - return prefix + chainIdHex + address + digestHex; -} diff --git a/pkg/wallet/src/types.ts b/pkg/wallet/src/types.ts new file mode 100644 index 0000000000..c8640f536e --- /dev/null +++ b/pkg/wallet/src/types.ts @@ -0,0 +1,22 @@ +/** + * Shared types for wallet package + */ + +/** + * EIP-712 Typed Data payload + */ +export interface TypedDataPayload { + types: { + EIP712Domain: Array<{ name: string; type: string }>; + [key: string]: Array<{ name: string; type: string }>; + }; + domain: { + name?: string; + version?: string; + chainId?: number | string; + verifyingContract?: string; + salt?: string; + }; + primaryType: string; + message: Record; +} diff --git a/pkg/wallet/src/utils/abi.ts b/pkg/wallet/src/utils/abi.ts index cab7f87205..c84e08359a 100644 --- a/pkg/wallet/src/utils/abi.ts +++ b/pkg/wallet/src/utils/abi.ts @@ -7,11 +7,10 @@ import { keccak256, toHex } from 'viem'; /** * Gets function selector from function signature * Replaces deprecated getFunctionSelector from viem - * + * * @param signature Function signature (e.g., 'transfer(address,uint256)') * @returns Function selector (first 4 bytes of keccak256 hash) */ export function getFunctionSelector(signature: string): `0x${string}` { return keccak256(toHex(signature)).slice(0, 10) as `0x${string}`; } - diff --git a/pkg/wallet/src/utils/chain.ts b/pkg/wallet/src/utils/chain.ts index e6890beb29..81035bfadc 100644 --- a/pkg/wallet/src/utils/chain.ts +++ b/pkg/wallet/src/utils/chain.ts @@ -8,4 +8,3 @@ export function getEip155ChainId(chainId: number): string { return `eip155:${chainId}`; } - diff --git a/pkg/wallet/src/utils/hex.ts b/pkg/wallet/src/utils/hex.ts index 0003dcd7ed..8fbe969353 100644 --- a/pkg/wallet/src/utils/hex.ts +++ b/pkg/wallet/src/utils/hex.ts @@ -3,8 +3,6 @@ * Simple helpers for hex manipulation (viem handles most conversions) */ -import { hexToString as viemHexToString } from 'viem'; - /** * Removes 0x prefix from hex string if present * Used for manual hex string manipulation (e.g., Sequence encoding) @@ -32,23 +30,3 @@ export function cleanSignature(sig: string, expectedLength?: number): string { } return cleaned; } - -/** - * Converts hex string to string - * Uses viem's hexToString directly - viem handles hex validation - * Note: personal_sign messages are typically already strings or properly formatted hex - */ -export function hexToString(hex: string): string { - // If not hex, return as-is (might already be a string) - if (!hex.startsWith('0x')) { - return hex; - } - - try { - return viemHexToString(hex as `0x${string}`); - } catch { - // If viem can't decode (invalid UTF-8), return hex as-is - return hex; - } -} - diff --git a/pkg/wallet/src/utils/http-client.ts b/pkg/wallet/src/utils/http-client.ts index c2680720d6..f9d45fca06 100644 --- a/pkg/wallet/src/utils/http-client.ts +++ b/pkg/wallet/src/utils/http-client.ts @@ -5,7 +5,7 @@ export interface AuthenticatedFetchOptions { method?: string; - body?: any; + body?: unknown; token?: string; headers?: Record; } @@ -13,9 +13,9 @@ export interface AuthenticatedFetchOptions { /** * Makes an authenticated HTTP request */ -export async function authenticatedFetch( +export async function authenticatedFetch( url: string, - options: AuthenticatedFetchOptions = {} + options: AuthenticatedFetchOptions = {}, ): Promise { const headers: Record = { 'Content-Type': 'application/json', @@ -23,14 +23,17 @@ export async function authenticatedFetch( }; if (options.token) { - headers['Authorization'] = `Bearer ${options.token}`; + headers.Authorization = `Bearer ${options.token}`; } - const response = await fetch(url, { + const fetchOptions: RequestInit = { method: options.method || 'GET', headers, - ...(options.body && { body: JSON.stringify(options.body) }), - }); + }; + if (options.body) { + fetchOptions.body = JSON.stringify(options.body); + } + const response = await fetch(url, fetchOptions); if (!response.ok) { const text = await response.text(); @@ -43,11 +46,11 @@ export async function authenticatedFetch( /** * Makes a JSON-RPC request (for relayer) */ -export async function jsonRpcRequest( +export async function jsonRpcRequest( url: string, method: string, - params: any[] = [], - token?: string + params: unknown[] = [], + token?: string, ): Promise { const body = { jsonrpc: '2.0', @@ -62,7 +65,7 @@ export async function jsonRpcRequest( method: 'POST', body, token, - } + }, ); if (response.error) { @@ -71,4 +74,3 @@ export async function jsonRpcRequest( return response.result as T; } - diff --git a/pkg/wallet/src/utils/subdigest.ts b/pkg/wallet/src/utils/subdigest.ts new file mode 100644 index 0000000000..1dbd791553 --- /dev/null +++ b/pkg/wallet/src/utils/subdigest.ts @@ -0,0 +1,18 @@ +import { removeHexPrefix } from './hex'; + +/** + * Encodes message sub-digest for Immutable wallet contract authentication + * Format: \x19\x01{chainId}{walletAddress}{digest} + */ +export function encodeMessageSubDigest( + chainId: bigint, + walletAddress: string, + digest: string, +): string { + const prefix = '\x19\x01'; + const chainIdHex = chainId.toString(16).padStart(64, '0'); + const address = removeHexPrefix(walletAddress).toLowerCase(); + const digestHex = removeHexPrefix(digest); + + return prefix + chainIdHex + address + digestHex; +} From 74512bb6e85dfc235f2afd853eba285009b49f22 Mon Sep 17 00:00:00 2001 From: Alex Connolly <25735635+alex-connolly@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:39:02 +1100 Subject: [PATCH 4/5] more --- pkg/auth/package.json | 2 - pnpm-lock.yaml | 645 ++++++++++++------------------------------ 2 files changed, 185 insertions(+), 462 deletions(-) diff --git a/pkg/auth/package.json b/pkg/auth/package.json index 12dbe3e386..c379ac31f9 100644 --- a/pkg/auth/package.json +++ b/pkg/auth/package.json @@ -5,8 +5,6 @@ "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { - "@imtbl/config": "workspace:*", - "@imtbl/metrics": "workspace:*", "oidc-client-ts": "3.3.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a82e8fdbdb..76fb75ef62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,7 +65,7 @@ importers: version: 17.1.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-react-refresh: specifier: latest - version: 0.4.19(eslint@8.57.0) + version: 0.4.24(eslint@8.57.0) events: specifier: ^3.1.0 version: 3.3.0 @@ -114,13 +114,13 @@ importers: version: 29.7.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)))(typescript@5.6.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2) + version: 10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2) typescript: specifier: ^5 version: 5.6.2 @@ -132,7 +132,7 @@ importers: version: 0.25.21(@emotion/react@11.11.3(@types/react@18.3.12)(react@18.3.1))(@rive-app/react-canvas-lite@4.9.0(react@18.3.1))(embla-carousel-react@8.1.5(react@18.3.1))(framer-motion@11.18.2(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@imtbl/sdk': specifier: latest - version: 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + version: 2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) next: specifier: 14.2.25 version: 14.2.25(@babel/core@7.26.9)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1196,7 +1196,7 @@ importers: version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) typescript: specifier: ^5.6.2 version: 5.6.2 @@ -1362,7 +1362,7 @@ importers: version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1374,7 +1374,7 @@ importers: version: 0.13.0(rollup@4.28.0) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)))(typescript@5.6.2) typescript: specifier: ^5.6.2 version: 5.6.2 @@ -2522,12 +2522,6 @@ importers: pkg/auth: dependencies: - '@imtbl/config': - specifier: workspace:* - version: link:../../packages/config - '@imtbl/metrics': - specifier: workspace:* - version: link:../../packages/internal/metrics oidc-client-ts: specifier: 3.3.0 version: 3.3.0 @@ -3738,9 +3732,11 @@ packages: '@cosmjs/crypto@0.31.3': resolution: {integrity: sha512-vRbvM9ZKR2017TO73dtJ50KxoGcFzKtKI7C8iO302BQ5p+DuB+AirUg1952UpSoLfv5ki9O416MFANNg8UN/EQ==} + deprecated: This uses elliptic for cryptographic operations, which contains several security-relevant bugs. To what degree this affects your application is something you need to carefully investigate. See https://github.com/cosmos/cosmjs/issues/1708 for further pointers. Starting with version 0.34.0 the cryptographic library has been replaced. However, private keys might still be at risk. '@cosmjs/crypto@0.32.4': resolution: {integrity: sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==} + deprecated: This uses elliptic for cryptographic operations, which contains several security-relevant bugs. To what degree this affects your application is something you need to carefully investigate. See https://github.com/cosmos/cosmjs/issues/1708 for further pointers. Starting with version 0.34.0 the cryptographic library has been replaced. However, private keys might still be at risk. '@cosmjs/encoding@0.31.3': resolution: {integrity: sha512-6IRtG0fiVYwyP7n+8e54uTx2pLYijO48V3t9TLiROERm5aUAIzIlz6Wp0NYaI5he9nh1lcEGJ1lkquVKFw3sUg==} @@ -4468,18 +4464,18 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} - '@imtbl/blockchain-data@2.1.11': - resolution: {integrity: sha512-FZtCxgBoDwNONdbLT61MiOzTG1+rMHC/Zt3ed0K79elISf73/v65SzhyHgumngOWkcUs25TiHw+jm2uU52JyBw==} + '@imtbl/blockchain-data@2.10.5': + resolution: {integrity: sha512-3jcqrQTEabWxSl1MQG0JgtKj91FkSZ7f0L6T29O3Kg00bMajuR9uM25Ksd+IxAEiaF/vKxNKPgU8YJcvnkDqdA==} - '@imtbl/bridge-sdk@2.1.11': - resolution: {integrity: sha512-EaXeMG+Ge17CT089wHipwYoJsGz/EeCLcEztTgIYpijA6R+4wb/jOtwnoCnOAWLHJE4Ep7wi366Xf7be5jTJWQ==} + '@imtbl/bridge-sdk@2.10.5': + resolution: {integrity: sha512-Zv6TwEyrC2VYNB1LE3dQ+E98nauDw2qvFkX8d53RWGMe6q8dJrEDzyqHpSAE/X+zdTi8ku76yWHYKL3Td3eM1A==} engines: {node: '>=20.11.0'} - '@imtbl/checkout-sdk@2.1.11': - resolution: {integrity: sha512-8PvuLX7T/3fGygug6fGGnAWRXFHpXkzV3wHgBcBekOe4K16Dl1wobgzyHoal6K90N/LId/ox38Hu3gPwe/yR6Q==} + '@imtbl/checkout-sdk@2.10.5': + resolution: {integrity: sha512-84cM0IsRBbrGjldnkKI95wpKcwlSG8E4TbxWLmQczWL8DPfsVbmWU02jZcRyS880ZE51nSRc0ecgqhnp6UG4jQ==} - '@imtbl/config@2.1.11': - resolution: {integrity: sha512-6qJ579F6teAGn8Rsdi+lIHejh4KoyYoG5QPiykMwFV+vbX7rok4pT6cNkNDLHFu/ZGcNMP+w5+W3vzaOnG0pcQ==} + '@imtbl/config@2.10.5': + resolution: {integrity: sha512-6ERcaOF38+2cuy4iJA8ZH4cSJFRxkDn7Yj9GXqayxh7eb6lJtFyyV5hnwwAfRxi0ef0ejcJAno7qAveHs45mkg==} engines: {node: '>=20.11.0'} '@imtbl/contracts@2.2.17': @@ -4488,51 +4484,51 @@ packages: '@imtbl/contracts@2.2.6': resolution: {integrity: sha512-2cfE3Tojfp4GnxwVKSwoZY1CWd+/drCIbCKawyH9Nh2zASXd7VC71lo27aD5RnCweXHkZVhPzjqwQf/xrtnmIQ==} - '@imtbl/dex-sdk@2.1.11': - resolution: {integrity: sha512-Neo2/ZaeT/DW6xm9xJ4GCFAvVOuBDjawKpWu2jRcu2t15Kmjj0qHHv1yKF5DHlSRq20fktytd+uJQyqtnx/+WA==} + '@imtbl/dex-sdk@2.10.5': + resolution: {integrity: sha512-pZxJHVvIo53WwVwZYhN0lsyfKzTeSHb2EEnhN1+JTXK+0H0s/bUOxJJtffBT1FkggGI8E21jV+Wwx7yTscXY1w==} engines: {node: '>=20.11.0'} - '@imtbl/generated-clients@2.1.11': - resolution: {integrity: sha512-r0xEwQiLYE9hOYCB/q37yPIkREpvRF+JeqQ3tXELQcqMwgH7Rb30ISAN2dMuxXMgvLa9pG2P9rSEisQXLemjJQ==} + '@imtbl/generated-clients@2.10.5': + resolution: {integrity: sha512-LDlrEgYwPRLWCp8OVV9ERSAW69bEDJUkqZGjg1K7ozUJYNBSmEIMGpVRz77vE1tZLBY7uvJReXOIeZRQ393Hog==} engines: {node: '>=20.11.0'} '@imtbl/image-resizer-utils@0.0.3': resolution: {integrity: sha512-/EOJKMJF4gD/Dv0qNhpUTpp2AgWeQ7XgYK9Xjl+xP2WWgaarNv1SHe1aeqSb8aZT5W7wSXdUGzn6TIxNsuCWGw==} - '@imtbl/metrics@2.1.11': - resolution: {integrity: sha512-d+WYjjbV4ufYL1xKr5mmxnbbkgWS5LKsJbZ8dTF0O7pICrsH2WY5J74R2RGjCVgfoWk28E67WTjsTJYwP+M5CA==} + '@imtbl/metrics@2.10.5': + resolution: {integrity: sha512-rwilndT8HH9xwmmQiK4ma18B4FYnmulJuTf2pVHTJboYPFB5oXkm6C0kf6ORTWLBv1QdEd3YQqJ7NOW+h1d4NA==} engines: {node: '>=20.11.0'} - '@imtbl/minting-backend@2.1.11': - resolution: {integrity: sha512-SgfOT+4nDMAxj5dq0pIrPyaXZ5fhUVgbfOGDoYGJd6x8jJ7utADFemLWWxZHII1/gTe5hg3xSkYR7uluzxvv+Q==} + '@imtbl/minting-backend@2.10.5': + resolution: {integrity: sha512-awOma8qs1qHIms/uGnfPc0XZGc3Kl6D/f3Q3ME1VbGcaH8hTlwng5nOtW9H1A0dlXY7YQm0wP8NJZtY+w2lyMQ==} - '@imtbl/orderbook@2.1.11': - resolution: {integrity: sha512-QKt+oc0AU4kQYCzRSBc0BRwkioZ30cfsmqzthtKU4OLg8H2ngjtt7qN9f6fylflJfHCI3T8spMJPvurH9qsK+w==} + '@imtbl/orderbook@2.10.5': + resolution: {integrity: sha512-FSS7eae0GEAdqnPqh9OgtGO3k3l+v4wSRz/N5/HPsMpz00iNm1OOqa+grswVenAu2UCTgilCCniPeHrRm74wNw==} - '@imtbl/passport@2.1.11': - resolution: {integrity: sha512-62bc8Dn/RwLJBQtGC8rR+UJ9wEPNUr1z9OlOK/YOezHR2RR9EAVyXaDkhquCN4LkZuw+iqYbu2OWWJ0ST3K8Eg==} + '@imtbl/passport@2.10.5': + resolution: {integrity: sha512-UK0W5hokWB+HO7az6nGuyEDWjRayFoOQXWawbVmVPFw+KHL2dbUTz+ps8Tu2NyQH8aNR1qVjBM66y/nhMYCCGQ==} engines: {node: '>=20.11.0'} '@imtbl/react-analytics@0.3.4-alpha': resolution: {integrity: sha512-4VWvfm8RZtpLub7+x2D2wNQ507nIVBCSAPA7B5lxdb0cKrHEujM6Y/HScMImHZHvgjUFQT1jiD9b2BL/DS43Pg==} - '@imtbl/sdk@2.1.11': - resolution: {integrity: sha512-w3oYF+THX6kL3kV/gRsAa9ca18QXb66jlGUPt//rEwOqu6M2mcpWb5V4R+SzR/gKp79OuSCzkPFKYF7kNqQOJw==} + '@imtbl/sdk@2.10.5': + resolution: {integrity: sha512-VAJVXrL1VhlKvZinxzy0FIbhtbTncCULo28tJn8pV3zvIz5M3RBQ8sLaWnVxywEDCbGmeJ6ZUcHE7dVeX1K8wA==} engines: {node: '>=20.0.0'} - '@imtbl/toolkit@2.1.11': - resolution: {integrity: sha512-krQRFKF+UL7qDca2eWBwRwDLWv0p+JlNs/bCO8q9xvv5gOkUvTplbm0hA+SfTsacboDJ13MekN96n83TRvvCPg==} + '@imtbl/toolkit@2.10.5': + resolution: {integrity: sha512-j0ToERYrILjZlf5YzjyuE3ovVdZ6RM5HTN5OlrrsAyJJfPQruv7wrW7rdKaXZXJIJLPeoyUO6L/K1yvLJCZp+A==} engines: {node: '>=20.11.0'} - '@imtbl/webhook@2.1.11': - resolution: {integrity: sha512-0uoXONxwroH1VYuNwKbqxxyLE83EZRZSUz1Gvya7uZk4RG8vAmSFqsPnEfqca64B4SYePoa0qeu0Bq8+P24AJg==} + '@imtbl/webhook@2.10.5': + resolution: {integrity: sha512-NkU2bWwPBmPY7mIzBPELRTqAIqjC9ZsUgY40p761Co9OSC29lfycbFhdXjfSOTbjUHPRUPmbs1W7h0a96uE4lQ==} - '@imtbl/x-client@2.1.11': - resolution: {integrity: sha512-HLYbj6dqfFvry5xfI1d+Q2GF+w5w9UTmAD4G29vTW6rsQQGXeBtJE5R8foY+c1OIz4+kDuTZrrfXxiGnkQ4DRQ==} + '@imtbl/x-client@2.10.5': + resolution: {integrity: sha512-hp2HCQkC6X+Cx0u394/Rk2GENyRXcPtWZ/FTXEwEL1PMcTu6mFaQy3Tv8On5f8nK0jUANy35IZLosbWaRes5iA==} engines: {node: '>=20.11.0'} - '@imtbl/x-provider@2.1.11': - resolution: {integrity: sha512-MdAv353DLWJf2S4JeBJpbLsfDbBjRcEc7baLTLxZUesfVTO6Mh0KLvuZ2U0vPtyOv37rG0oeXzcmWaq8a3CGgQ==} + '@imtbl/x-provider@2.10.5': + resolution: {integrity: sha512-AmRG14vGPq118BF8dK1Wm5CHsPhtlM0PKY8AcDgk/ywc2aHDH6+7pffjffWVzQZJq8CSyKGD3M3q4WtKZLCQ0w==} engines: {node: '>=20.11.0'} '@ioredis/commands@1.2.0': @@ -4822,11 +4818,9 @@ packages: resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==} engines: {node: '>=8'} - '@magic-ext/oidc@12.0.2': - resolution: {integrity: sha512-k7KdSprnOFQjyjO24qJX4qnrhZJjZBva2f32REpvo5sb37AbWaYcmA4F+FfhWhMXxwdHlzFwSkeWHgFvzInEgw==} - '@magic-ext/oidc@12.0.5': resolution: {integrity: sha512-EAmmRRZn/c5jmxHZ1H3IHtEqUKHYrsRtH9O+WuMFOZMv0llef/9MBa4DiRZkpnB0EPKb2hwsY7us8qk/LaFRNA==} + deprecated: 'Deprecation Notice: The OIDC extension will be deprecated soon. Please migrate to API Wallet, which offers improved performance and faster response times. Learn more: https://docs.magic.link/api-wallets/introduction' '@magic-sdk/commons@25.0.5': resolution: {integrity: sha512-/qXYCAs4Y8XISyTHzytoWf4CDejLOynW53X9XFnGJt9c6jFV7FoeuN0n/+TIngjHVUu3v+wbQoJNeFzzCE2y5g==} @@ -7651,6 +7645,7 @@ packages: '@uniswap/swap-router-contracts@1.3.1': resolution: {integrity: sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg==} engines: {node: '>=10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. '@uniswap/v2-core@1.0.1': resolution: {integrity: sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==} @@ -7713,6 +7708,7 @@ packages: '@walletconnect/ethereum-provider@2.13.0': resolution: {integrity: sha512-dnpW8mmLpWl1AZUYGYZpaAfGw1HFkL0WSlhk5xekx3IJJKn4pLacX2QeIOo0iNkzNQxZfux1AK4Grl1DvtzZEA==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/events@1.0.1': resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} @@ -7754,6 +7750,7 @@ packages: '@walletconnect/modal@2.6.2': resolution: {integrity: sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA==} + deprecated: Please follow the migration guide on https://docs.reown.com/appkit/upgrade/wcm '@walletconnect/relay-api@1.0.10': resolution: {integrity: sha512-tqrdd4zU9VBNqUaXXQASaexklv6A54yEyQQEXYOCr+Jz8Ket0dmPBDyg19LVSNUN2cipAghQc45/KVmfFJ0cYw==} @@ -7766,6 +7763,7 @@ packages: '@walletconnect/sign-client@2.13.0': resolution: {integrity: sha512-En7KSvNUlQFx20IsYGsFgkNJ2lpvDvRsSFOT5PTdGskwCkUfOpB33SQJ6nCrN19gyoKPNvWg80Cy6MJI0TjNYA==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/time@1.0.2': resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} @@ -7775,6 +7773,7 @@ packages: '@walletconnect/universal-provider@2.13.0': resolution: {integrity: sha512-B5QvO8pnk5Bqn4aIt0OukGEQn2Auk9VbHfhQb9cGwgmSCd1GlprX/Qblu4gyT5+TjHMb1Gz5UssUaZWTWbDhBg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/utils@2.13.0': resolution: {integrity: sha512-q1eDCsRHj5iLe7fF8RroGoPZpdo2CYMZzQSrw1iqL+2+GOeqapxxuJ1vaJkmDUkwgklfB22ufqG6KQnz78sD4w==} @@ -10217,8 +10216,8 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-refresh@0.4.19: - resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + eslint-plugin-react-refresh@0.4.24: + resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} peerDependencies: eslint: '>=8.40' @@ -13489,10 +13488,6 @@ packages: ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} - oidc-client-ts@2.4.0: - resolution: {integrity: sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==} - engines: {node: '>=12.13.0'} - oidc-client-ts@3.3.0: resolution: {integrity: sha512-t13S540ZwFOEZKLYHJwSfITugupW4uYLwuQSSXyKH/wHwZ+7FvgHE7gnNJh1YQIZ1Yd1hKSRjqeXGSUtS0r9JA==} engines: {node: '>=18'} @@ -20213,17 +20208,17 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} - '@imtbl/blockchain-data@2.1.11': + '@imtbl/blockchain-data@2.10.5': dependencies: - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 axios: 1.7.7 transitivePeerDependencies: - debug - '@imtbl/bridge-sdk@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/bridge-sdk@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.1.11 + '@imtbl/config': 2.10.5 '@jest/globals': 29.7.0 axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20233,16 +20228,16 @@ snapshots: - supports-color - utf-8-validate - '@imtbl/checkout-sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': + '@imtbl/checkout-sdk@2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: - '@imtbl/blockchain-data': 2.1.11 - '@imtbl/bridge-sdk': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/config': 2.1.11 - '@imtbl/dex-sdk': 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - '@imtbl/generated-clients': 2.1.11 - '@imtbl/metrics': 2.1.11 - '@imtbl/orderbook': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/passport': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/blockchain-data': 2.10.5 + '@imtbl/bridge-sdk': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/config': 2.10.5 + '@imtbl/dex-sdk': 2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@imtbl/generated-clients': 2.10.5 + '@imtbl/metrics': 2.10.5 + '@imtbl/orderbook': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/passport': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@metamask/detect-provider': 2.0.0 axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20255,9 +20250,9 @@ snapshots: - supports-color - utf-8-validate - '@imtbl/config@2.1.11': + '@imtbl/config@2.10.5': dependencies: - '@imtbl/metrics': 2.1.11 + '@imtbl/metrics': 2.10.5 transitivePeerDependencies: - debug @@ -20304,9 +20299,9 @@ snapshots: - typescript - utf-8-validate - '@imtbl/dex-sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': + '@imtbl/dex-sdk@2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.1.11 + '@imtbl/config': 2.10.5 '@uniswap/sdk-core': 3.2.3 '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) '@uniswap/v3-sdk': 3.10.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) @@ -20317,7 +20312,7 @@ snapshots: - hardhat - utf-8-validate - '@imtbl/generated-clients@2.1.11': + '@imtbl/generated-clients@2.10.5': dependencies: axios: 1.7.7 transitivePeerDependencies: @@ -20327,7 +20322,7 @@ snapshots: dependencies: buffer: 6.0.3 - '@imtbl/metrics@2.1.11': + '@imtbl/metrics@2.10.5': dependencies: axios: 1.7.7 global-const: 0.1.2 @@ -20335,13 +20330,13 @@ snapshots: transitivePeerDependencies: - debug - '@imtbl/minting-backend@2.1.11': + '@imtbl/minting-backend@2.10.5': dependencies: - '@imtbl/blockchain-data': 2.1.11 - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 - '@imtbl/metrics': 2.1.11 - '@imtbl/webhook': 2.1.11 + '@imtbl/blockchain-data': 2.10.5 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 + '@imtbl/metrics': 2.10.5 + '@imtbl/webhook': 2.10.5 uuid: 8.3.2 optionalDependencies: pg: 8.11.5 @@ -20350,10 +20345,10 @@ snapshots: - debug - pg-native - '@imtbl/orderbook@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/orderbook@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.1.11 - '@imtbl/metrics': 2.1.11 + '@imtbl/config': 2.10.5 + '@imtbl/metrics': 2.10.5 '@opensea/seaport-js': 4.0.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20364,17 +20359,17 @@ snapshots: - debug - utf-8-validate - '@imtbl/passport@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/passport@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@0xsequence/abi': 2.2.13 '@0xsequence/core': 2.2.13(ethers@6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 - '@imtbl/metrics': 2.1.11 - '@imtbl/toolkit': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/x-client': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/x-provider': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@magic-ext/oidc': 12.0.2 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 + '@imtbl/metrics': 2.10.5 + '@imtbl/toolkit': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/x-client': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/x-provider': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@magic-ext/oidc': 12.0.5 '@magic-sdk/provider': 29.0.5(localforage@1.10.0) '@metamask/detect-provider': 2.0.0 axios: 1.7.7 @@ -20383,7 +20378,7 @@ snapshots: jwt-decode: 3.1.2 localforage: 1.10.0 magic-sdk: 29.0.5 - oidc-client-ts: 2.4.0 + oidc-client-ts: 3.3.0 uuid: 8.3.2 transitivePeerDependencies: - bufferutil @@ -20398,17 +20393,17 @@ snapshots: - encoding - supports-color - '@imtbl/sdk@2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': + '@imtbl/sdk@2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: - '@imtbl/blockchain-data': 2.1.11 - '@imtbl/checkout-sdk': 2.1.11(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - '@imtbl/config': 2.1.11 - '@imtbl/minting-backend': 2.1.11 - '@imtbl/orderbook': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/passport': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/webhook': 2.1.11 - '@imtbl/x-client': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/x-provider': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/blockchain-data': 2.10.5 + '@imtbl/checkout-sdk': 2.10.5(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@imtbl/config': 2.10.5 + '@imtbl/minting-backend': 2.10.5 + '@imtbl/orderbook': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/passport': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/webhook': 2.10.5 + '@imtbl/x-client': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/x-provider': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - debug @@ -20417,35 +20412,35 @@ snapshots: - supports-color - utf-8-validate - '@imtbl/toolkit@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/toolkit@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/x-client': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@magic-ext/oidc': 12.0.2 + '@imtbl/x-client': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@magic-ext/oidc': 12.0.5 '@metamask/detect-provider': 2.0.0 axios: 1.7.7 bn.js: 5.2.1 enc-utils: 3.0.0 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) magic-sdk: 29.0.5 - oidc-client-ts: 2.4.0 + oidc-client-ts: 3.3.0 transitivePeerDependencies: - bufferutil - debug - utf-8-validate - '@imtbl/webhook@2.1.11': + '@imtbl/webhook@2.10.5': dependencies: - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 sns-validator: 0.3.5 transitivePeerDependencies: - debug - '@imtbl/x-client@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/x-client@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@ethereumjs/wallet': 2.0.4 - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 axios: 1.7.7 bn.js: 5.2.1 elliptic: 6.6.1 @@ -20457,19 +20452,19 @@ snapshots: - debug - utf-8-validate - '@imtbl/x-provider@2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/x-provider@2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.1.11 - '@imtbl/generated-clients': 2.1.11 - '@imtbl/toolkit': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/x-client': 2.1.11(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@magic-ext/oidc': 12.0.2 + '@imtbl/config': 2.10.5 + '@imtbl/generated-clients': 2.10.5 + '@imtbl/toolkit': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/x-client': 2.10.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@magic-ext/oidc': 12.0.5 '@metamask/detect-provider': 2.0.0 axios: 1.7.7 enc-utils: 3.0.0 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) magic-sdk: 29.0.5 - oidc-client-ts: 2.4.0 + oidc-client-ts: 3.3.0 transitivePeerDependencies: - bufferutil - debug @@ -20610,43 +20605,6 @@ snapshots: - ts-node - utf-8-validate - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0(node-notifier@8.0.2) - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.14.13 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.8.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.5 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - optionalDependencies: - node-notifier: 8.0.2 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 @@ -21187,8 +21145,6 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 - '@magic-ext/oidc@12.0.2': {} - '@magic-ext/oidc@12.0.5': {} '@magic-sdk/commons@25.0.5(@magic-sdk/provider@29.0.5(localforage@1.10.0))(@magic-sdk/types@24.18.1)': @@ -24788,25 +24744,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/utils': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - debug: 4.3.7(supports-color@8.1.1) - eslint: 9.16.0(jiti@1.21.0) - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare-lite: 1.4.0 - semver: 7.7.1 - tsutils: 3.21.0(typescript@5.6.2) - optionalDependencies: - typescript: 5.6.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -26040,6 +25977,20 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.26.9): + dependencies: + '@babel/core': 7.26.9 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.9) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-loader@8.3.0(@babel/core@7.26.9)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)): dependencies: '@babel/core': 7.26.9 @@ -26240,6 +26191,13 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) + babel-preset-jest@29.6.3(@babel/core@7.26.9): + dependencies: + '@babel/core': 7.26.9 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.9) + optional: true + babel-preset-react-app@10.0.1: dependencies: '@babel/core': 7.26.9 @@ -27171,21 +27129,6 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - create-jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 @@ -27216,21 +27159,6 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - create-jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 @@ -28182,7 +28110,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 @@ -28193,13 +28121,13 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.0) @@ -28214,7 +28142,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -28232,8 +28160,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -28251,8 +28179,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -28270,7 +28198,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -28288,7 +28216,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -28298,45 +28226,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): - dependencies: - '@babel/core': 7.26.9 - '@babel/eslint-parser': 7.22.9(@babel/core@7.26.9)(eslint@8.57.0) - '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - babel-preset-react-app: 10.0.1 - confusing-browser-globals: 1.0.11 - eslint: 8.57.0 - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) - eslint-plugin-react: 7.35.0(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) - eslint-plugin-testing-library: 5.11.0(eslint@8.57.0)(typescript@5.6.2) - optionalDependencies: - typescript: 5.6.2 - transitivePeerDependencies: - - '@babel/plugin-syntax-flow' - - '@babel/plugin-transform-react-jsx' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - jest - - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): dependencies: '@babel/core': 7.26.9 '@babel/eslint-parser': 7.22.9(@babel/core@7.26.9)(eslint@9.16.0(jiti@1.21.0)) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) '@typescript-eslint/parser': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 eslint: 9.16.0(jiti@1.21.0) eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) eslint-plugin-jsx-a11y: 6.9.0(eslint@9.16.0(jiti@1.21.0)) eslint-plugin-react: 7.35.0(eslint@9.16.0(jiti@1.21.0)) @@ -28352,23 +28253,23 @@ snapshots: - jest - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): dependencies: '@babel/core': 7.26.9 - '@babel/eslint-parser': 7.22.9(@babel/core@7.26.9)(eslint@9.16.0(jiti@1.21.0)) + '@babel/eslint-parser': 7.22.9(@babel/core@7.26.9)(eslint@8.57.0) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/parser': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 - eslint: 9.16.0(jiti@1.21.0) - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-plugin-jsx-a11y: 6.9.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-react: 7.35.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-react-hooks: 4.6.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-testing-library: 5.11.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + eslint: 8.57.0 + eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) + eslint-plugin-react: 7.35.0(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) + eslint-plugin-testing-library: 5.11.0(eslint@8.57.0)(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -28387,31 +28288,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 4.3.7(supports-color@8.1.1) - enhanced-resolve: 5.15.0 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - get-tsconfig: 4.6.2 - globby: 13.2.2 - is-core-module: 2.15.0 - is-glob: 4.0.3 - synckit: 0.8.5 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.7(supports-color@8.1.1) enhanced-resolve: 5.15.0 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) get-tsconfig: 4.6.2 globby: 13.2.2 is-core-module: 2.15.0 @@ -28423,17 +28306,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -28445,16 +28317,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.16.0(jiti@1.21.0)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - eslint: 9.16.0(jiti@1.21.0) - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.16.0(jiti@1.21.0)): dependencies: debug: 3.2.7 @@ -28465,14 +28327,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0): - dependencies: - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) - eslint: 8.57.0 - lodash: 4.17.21 - string-natural-compare: 3.0.1 - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0)): dependencies: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.10) @@ -28481,15 +28335,15 @@ snapshots: lodash: 4.17.21 string-natural-compare: 3.0.1 - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0)): + eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0): dependencies: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.9) '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.9) - eslint: 9.16.0(jiti@1.21.0) + eslint: 8.57.0 lodash: 4.17.21 string-natural-compare: 3.0.1 - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -28499,34 +28353,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.15.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.16.0(jiti@1.21.0) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.16.0(jiti@1.21.0)) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -28592,17 +28419,6 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): - dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - eslint: 9.16.0(jiti@1.21.0) - optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - jest: 27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10) - transitivePeerDependencies: - - supports-color - - typescript - eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0): dependencies: aria-query: 5.1.3 @@ -28666,7 +28482,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-react-refresh@0.4.19(eslint@8.57.0): + eslint-plugin-react-refresh@0.4.24(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -31081,11 +30897,11 @@ snapshots: jest-cli@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0) + create-jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.1.0 jest-config: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) @@ -31144,11 +30960,11 @@ snapshots: jest-cli@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0) + create-jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.1.0 jest-config: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) @@ -32260,7 +32076,7 @@ snapshots: jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.1.0 jest-cli: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) @@ -32302,7 +32118,7 @@ snapshots: jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.1.0 jest-cli: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) @@ -33860,11 +33676,6 @@ snapshots: ohash@1.1.3: {} - oidc-client-ts@2.4.0: - dependencies: - crypto-js: 4.2.0 - jwt-decode: 3.1.2 - oidc-client-ts@3.3.0: dependencies: jwt-decode: 4.0.0 @@ -35318,92 +35129,6 @@ snapshots: '@remix-run/router': 1.7.2 react: 18.3.1 - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): - dependencies: - '@babel/core': 7.26.9 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)))(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - '@svgr/webpack': 5.5.0 - babel-jest: 27.5.1(@babel/core@7.26.9) - babel-loader: 8.3.0(@babel/core@7.26.9)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - babel-plugin-named-asset-import: 0.3.8(@babel/core@7.26.9) - babel-preset-react-app: 10.0.1 - bfj: 7.0.2 - browserslist: 4.23.3 - camelcase: 6.3.0 - case-sensitive-paths-webpack-plugin: 2.4.0 - css-loader: 6.8.1(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.1)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - dotenv: 10.0.0 - dotenv-expand: 5.1.0 - eslint: 8.57.0 - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-webpack-plugin: 3.2.0(eslint@8.57.0)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - file-loader: 6.2.0(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - fs-extra: 10.1.0 - html-webpack-plugin: 5.5.3(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - identity-obj-proxy: 3.0.0 - jest: 27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10) - jest-resolve: 27.5.1 - jest-watch-typeahead: 1.1.0(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10)) - mini-css-extract-plugin: 2.7.6(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - postcss: 8.4.49 - postcss-flexbugs-fixes: 5.0.2(postcss@8.4.49) - postcss-loader: 6.2.1(postcss@8.4.49)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - postcss-normalize: 10.0.1(browserslist@4.23.3)(postcss@8.4.49) - postcss-preset-env: 7.8.3(postcss@8.4.49) - prompts: 2.4.2 - react: 18.3.1 - react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@8.57.0)(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - react-refresh: 0.11.0 - resolve: 1.22.8 - resolve-url-loader: 4.0.0 - sass-loader: 12.6.0(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - semver: 7.6.3 - source-map-loader: 3.0.2(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - style-loader: 3.3.3(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - tailwindcss: 3.4.7(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) - terser-webpack-plugin: 5.3.9(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - webpack: 5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1) - webpack-dev-server: 4.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - webpack-manifest-plugin: 4.1.1(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) - optionalDependencies: - fsevents: 2.3.3 - typescript: 5.6.2 - transitivePeerDependencies: - - '@babel/plugin-syntax-flow' - - '@babel/plugin-transform-react-jsx' - - '@parcel/css' - - '@swc/core' - - '@types/babel__core' - - '@types/webpack' - - bufferutil - - canvas - - clean-css - - csso - - debug - - esbuild - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - fibers - - node-notifier - - node-sass - - rework - - rework-visit - - sass - - sass-embedded - - sockjs-client - - supports-color - - ts-node - - type-fest - - uglify-js - - utf-8-validate - - vue-template-compiler - - webpack-cli - - webpack-hot-middleware - - webpack-plugin-serve - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.26.9 @@ -35490,7 +35215,7 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.26.9 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)))(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) @@ -35507,9 +35232,9 @@ snapshots: css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.1)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) dotenv: 10.0.0 dotenv-expand: 5.1.0 - eslint: 9.16.0(jiti@1.21.0) - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-webpack-plugin: 3.2.0(eslint@9.16.0(jiti@1.21.0))(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) + eslint: 8.57.0 + eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-webpack-plugin: 3.2.0(eslint@8.57.0)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) file-loader: 6.2.0(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) fs-extra: 10.1.0 html-webpack-plugin: 5.5.3(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) @@ -35526,7 +35251,7 @@ snapshots: prompts: 2.4.2 react: 18.3.1 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) + react-dev-utils: 12.0.1(eslint@8.57.0)(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(esbuild@0.23.1)) react-refresh: 0.11.0 resolve: 1.22.8 resolve-url-loader: 4.0.0 @@ -37314,12 +37039,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -37334,12 +37059,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.23.1 - ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + jest: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -37354,12 +37079,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.23.1 - ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2)) + jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -37374,12 +37099,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.23.1 - ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(esbuild@0.23.1)(jest@29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.5)(typescript@5.6.2)) + jest: 29.7.0(@types/node@18.15.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -37388,10 +37113,10 @@ snapshots: typescript: 5.6.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.26.9 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) + babel-jest: 29.7.0(@babel/core@7.26.9) esbuild: 0.23.1 ts-mockito@2.6.1: From 9cfed46aac682b4da8c813b923a47f3c9568e33a Mon Sep 17 00:00:00 2001 From: Alex Connolly <25735635+alex-connolly@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:31:16 +1100 Subject: [PATCH 5/5] wallet only mode --- .gitignore | 1 + pkg/wallet/src/provider.ts | 47 ++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index b1a2343601..c7ca55f8fc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ yalc.lock package-lock.json docs/ +.cursor-docs/ diff --git a/pkg/wallet/src/provider.ts b/pkg/wallet/src/provider.ts index 73fe50b7c9..eac8defd77 100644 --- a/pkg/wallet/src/provider.ts +++ b/pkg/wallet/src/provider.ts @@ -2,7 +2,7 @@ * EIP-1193 compatible provider for Immutable zkEVM */ -import type { User, Auth } from '@imtbl/auth'; +import { type User, Auth } from '@imtbl/auth'; import { toHex, fromHex, createPublicClient, http, isAddress, hexToString, } from 'viem'; @@ -287,6 +287,20 @@ export class PassportEVMProvider implements Provider { }); } + /** + * Creates a shared auth client for wallet-only mode + * Used when apps don't provide their own OAuth client + * @internal + */ + // eslint-disable-next-line class-methods-use-this + private createSharedAuthClient(): Auth { + return new Auth({ + clientId: 'immutable-passport-wallet-only', // Shared OAuth client ID for wallet-only mode + redirectUri: 'https://passport.immutable.com/wallet-callback', // This callback page is hosted by Passport and handles OAuth redirects + scope: 'openid transact', // Wallet-only scope, no profile/email access + }); + } + /** * Sets signer (internal use only) * @internal @@ -380,29 +394,42 @@ export class PassportEVMProvider implements Provider { /** * Ensures user is authenticated, triggers login automatically if auth client provided + * If no auth client is provided, creates a shared auth client for wallet-only mode */ private async ensureAuthenticated(): Promise { if (this.authenticatedUser) { return; } - if (this.auth) { - const user = await this.auth.loginPopup(); - if (user) { - this.setAuthenticatedUser(user); - return; - } + // If no auth client provided, create shared client for wallet-only mode + if (!this.auth) { + this.auth = this.createSharedAuthClient(); + } + + // Attempt to get existing user first (checks localStorage/session) + const existingUser = await this.auth.getUser(); + if (existingUser) { + this.setAuthenticatedUser(existingUser); + return; + } + + // No existing user, trigger login popup + const user = await this.auth.loginPopup(); + if (user) { + this.setAuthenticatedUser(user); + return; } throw new JsonRpcError( ProviderErrorCode.UNAUTHORIZED, - 'User not authenticated. Please provide an auth client or login first.', + 'User not authenticated. Login popup was closed or cancelled.', ); } /** * Handles eth_requestAccounts - * Automatically triggers login if auth client is provided and user not authenticated + * Automatically triggers login if user not authenticated + * If no auth client is provided, uses shared client ID for wallet-only mode */ private async handleRequestAccounts(): Promise { // Check if we already have a wallet address @@ -412,7 +439,7 @@ export class PassportEVMProvider implements Provider { return [address]; } - // Ensure authenticated (will auto-login if auth client provided) + // Ensure authenticated (will auto-login using shared client if no auth provided) await this.ensureAuthenticated(); // Ensure signer is set