Universal Profiles - Browser Extension
Contributor - background controllers and the DDD business-logic layer (later extracted into shared npm packages reused across the extension and LUKSO's mobile apps) · Sept 2023 – July 2026
Production wallet browser extension (Manifest V2 + V3) for LUKSO Universal Profiles - smart-contract accounts with granular on-chain permissions, gasless meta-transactions, and human-readable transaction decoding.












Universal Profiles are LUKSO's smart-contract accounts (LSP0 ERC725Account) owned by an LSP6 KeyManager, which grants named permissions - like SETDATA, CALL, DEPLOY, EDITPERMISSIONS, ADDCONTROLLER - to individual controller keys. This extension is the consumer wallet for that model: it injects an EIP-1193 provider into dApps, manages profiles and their controllers, presents permission changes in plain language, and can pay gas via a relay service so users transact without holding LYX.
Because the account lives on-chain rather than in a private key, there is no seed-phrase UI at all. Recovery works through encrypted JSON backup files, a QR-based cross-device import flow, where an already-authorized controller grants permissions to the new device or a restore flow through optionally configured controller key managed by LUKSO via https://my.universalprofile.cloud.
The extension runs a four-context topology - injected inpage provider → content script → background service worker → popup - with JSON-RPC multiplexed over duplex port streams. Above that, the business-logic layer is organized as DDD bounded contexts with a real domain / use-cases / infrastructure split on top of a shared @lukso/controllerapps-sharedlogic-ddd-core package.
Architecture & engineering highlights
- Wallet feature set - onboarding and password setup, dApp connection approval, decoded transaction confirmation, message signing including SIWE, per-profile controller and permission management, gasless relayer configuration and quota, multi-network switching across LUKSO mainnet + testnet, Ethereum and Base, and many others, activity history, profile editing with IPFS image upload, QR cross-device import, encrypted backup export and restore, and configurable auto-lock.
- Transaction-intent decoder with on-chain disambiguation - maps calldata to a set of human-readable renderers. Selectors are computed lazily from human-readable signatures rather than hardcoded, so they cannot drift out of sync, and ambiguous ones are resolved at confirmation time by live eth_call probes.
- Sandboxed rendering of untrusted dApp data - decoded transaction content (token names, remote images, arbitrary calldata) renders inside an isolated iframe document rather than in the wallet's own DOM, so hostile metadata can never reach privileged UI.
- LSP25 gasless meta-transactions - builds the relay-call digest, signs it using EIP-191 with the KeyManager as intended validator, and POSTs it to a relay service, with nonce channels, per-profile quota gating, and typed error paths.
- Multi-target build for the extension's platform constraints - three ordered webpack compilers and a custom plugin that emits both V2 and V3 manifests from a single build. The MV3 service-worker compiler caps output at one chunk (Chrome forbids importScripts() after install) and forces inline source maps (MV3's CSP bans unsafe-eval); the inpage provider bundle is read off disk at build time and inlined as a string constant for page-world injection.
- Domain layer extracted into shared packages - the backup / profile / keyring domain I built in-tree was factored out into @lukso/controllerapps-sharedlogic-* and re-integrated as a dependency, so the same domain logic now backs both the extension and LUKSO's mobile wallets.
- Three transaction safeguards for failure modes that can brick a smart-contract account: blocking a transaction that would strip the extension's own controller permissions (self-lockout), preventing a profile from being set as its own controller, and rejecting no-op permission changes.
- Wallet discovery and delivery - EIP-6963 multi-wallet discovery claims window.lukso and sets window.ethereum only when no other wallet owns it; the release pipeline runs end to end (release-please → draft GitHub prerelease → Chrome Web Store upload and publish via OAuth token exchange), with a Playwright harness that launches an unpacked extension in headed Chrome and tests split across three Jest configs feeding parallel CI jobs.
Tech stack
Extension & UI
Web3 & crypto
Extension platform & transport
Storage
Build & tooling
Testing
All rights belong to their respective owners.