Diving Deeper into Ghost Wallets & Auto-signing
In the previous article, we introduced Ghost Wallets and auto-signing. In this one, we delve deeper into the problem it solves, the design principles behind it, and its technical architecture.
At its core, the feature combines Privy's embedded wallets with the Cosmos SDK's authz and feegrant modules to create a session-key like experience. The design enables no pop-up signing for Initia appchains using the Initia Wallet with minimal code changes, works across all VMs in the Interwoven Stack, and preserves users' existing addresses.
UX Problem with Onchain Wallets and Signatures
Onchain apps still suffer from high-friction wallet interactions. Users often need to approve a wallet pop-up for every action, slowing time-sensitive flows (trading, gaming) and degrading the user experience.
While there are generally accepted solutions such as embedded wallets and session keys, both of these have limitations.
Embedded wallets simplify onboarding for new users but force existing users to adopt new addresses and migrate assets, positions, and other application state.
Session keys and account abstraction features are relatively mature in EVM ecosystems but are practically unavailable to non-EVM applications, often leaving developers to build and maintain custom infra.
The Design behind a Ghost Wallet
A Ghost Wallet is an embedded account that executes transactions on behalf of the user's Main Wallet (browser or hardware). Crucially:
- The Ghost Wallet never holds funds.
- Users don't see, manage, or fund the Ghost Wallet.
- Users keep their existing Main Wallet address and assets.
The Ghost Wallet itself is created through a wallet provider (e.g. Privy). Its functionality is then achieved by leveraging two Cosmos SDK modules built into all Initia appchains:
- authz: the Main Wallet grants scoped execution rights to the Ghost Wallet for specific messages and durations.
- feegrant: the Main Wallet pays fees on behalf of the Ghost Wallet.
When a supported app initiates a transaction, Initia Wallet wraps the intended messages in an authz MsgExec for the Ghost Wallet to execute. The chain enforces that:
- Messages fall within the scope granted by authz.
- Fees are paid by the Main Wallet on behalf of the Ghost Wallet via feegrant.
As the transaction is executed by the Ghost Wallet instead of the user's browser wallet, they never need to manually confirm the transaction.
This design has several key desirable properties:
- No address changes, no migrations. Users can keep using their browser or other wallets
- No manual approvals during the session: Minimize action latency.
- Strict scoping: Authz allows explicit scoping of the transaction types the Ghost Wallet is allowed to send on the Main Wallet's behalf and how long the permission lasts for.
Auto-signing Example with Civitia
A current Civitia user retains their existing address, balances, positions, and in-game stats. With Ghost Wallets, they enable auto-signing once and continue interacting without pop-ups.
Setup flow
After grants succeed, auto-signing is active. Users can set duration and revoke or renew at any time. This enables users to use the application for the specified time period without needing to sign new transactions. Time period implementation is a critical feature as it ensures the ability to limit the duration of access, hence minimizing risk.
Once the user has given the necessary permissions, they can start using autosign right away. At first glance, nothing changes for them. They still connect their browser wallet and see the assets, positions, and other game data that they own. However, now when they try to perform an in-game action, they no longer need to confirm separately in their wallet. Instead, the transaction executes immediately.
Integration
To use Ghost Wallets, simply install or update InterwovenKit to the latest version and add Privy's react library to your frontend application.
pnpm add @initia/interwovenkit-reactpnpm add @privy-io/react-auth
Then update your providers.tsx as follows:
import { useEffect } from "react"import { PrivyProvider, useCreateWallet, useCrossAppAccounts, usePrivy, useWallets } from "@privy-io/react-auth"import { createConfig, WagmiProvider } from "wagmi"import { http } from "wagmi"import { mainnet } from "wagmi/chains"import { QueryClient, QueryClientProvider } from "@tanstack/react-query"import { injectStyles, InterwovenKitProvider, PRIVY_APP_ID } from "@initia/interwovenkit-react"import css from "@initia/interwovenkit-react/styles.css?inline"const wagmiConfig = createConfig({multiInjectedProviderDiscovery: false,chains: [mainnet],transports: { [mainnet.id]: http() },})const queryClient = new QueryClient()function InterwovenKitWrapper({ children }) {const privy = usePrivy()const crossAppAccounts = useCrossAppAccounts()const { createWallet } = useCreateWallet()const { wallets } = useWallets()return (<InterwovenKitProviderprivyContext={{ privy, crossAppAccounts, createWallet, wallets }}enableAutoSign>{children}</InterwovenKitProvider>)}export default function Providers() {useEffect(() => {injectStyles(css)}, [])return (<PrivyProviderappId="YOUR_PRIVY_APP_ID"config={{appearance: {theme: "dark",},embeddedWallets: {ethereum: { createOnLogin: "all-users" },showWalletUIs: false,},loginMethodsAndOrder: {primary: [`privy:${PRIVY_APP_ID}`, "detected_ethereum_wallets"],},}}><QueryClientProvider client={queryClient}><WagmiProvider config={wagmiConfig}><InterwovenKitWrapper><App /></InterwovenKitWrapper></WagmiProvider></QueryClientProvider></PrivyProvider>)}
Once set up, Ghost Wallet functionalities are now accessible through the autosign prop in useInterwovenKit.
import { useInterwovenKit } from "@initia/interwovenkit-react"export default function YourAppHeader() {const { autoSign, submitTxBlock } = useInterwovenKit()// Check if auto sign is loadingif (autoSign.isLoading) {return "Loading..."}// Check expiration statusconst isAutoSignActive = autoSign.expiration && autoSign.expiration > new Date()return isAutoSignActive ? (<button onClick={() => autoSign.disable()}>Disable Auto Sign</button>) : (<button onClick={() => autoSign.enable()}>Enable Auto Sign</button>)}
Conclusion
With Ghost Wallets and AutoSign, appchains can now easily and seamlessly deliver low-friction, secure interactions and user experience across any VM. If you're a developer or team that's looking to build or leverage such features, we'd love to hear from you.