Redesigning Autosign Wallets
On most blockchains, every action requires a signature. Sending funds, swapping tokens, and interacting with smart contracts all trigger a wallet prompt asking the user to review and approve the transaction. In apps where users perform many small actions in succession, such as games or trading interfaces, that friction quickly becomes part of the product.
Autosign in InterwovenKit was our attempt to remove that friction without weakening the permission model. When a user enables Autosign, their main wallet grants a secondary wallet permission to execute a limited set of transactions on its behalf. That delegate wallet can then sign those transactions in the background, so the user no longer has to manually approve every repeat action or move assets into a separate wallet.
The challenge is that removing repeated wallet prompts is only half of the problem. The other half is deciding what that delegate wallet should actually be. It needs to be easy for developers to integrate, compatible with normal browser wallets, and safe enough that we are not replacing one UX problem with a worse key-management problem.
Our first implementation used Privy embedded wallets as the delegated signer. That let us ship quickly by outsourcing wallet creation and key storage. Over time, though, the tradeoffs became harder to justify.
Three issues mattered most:
- Developer setup: teams had to install and pay for Privy separately, wrap their app in its provider, and manage another set of API credentials.
- Environment management: Privy's domain allowlisting is a valuable safeguard, but it also meant registering every localhost port, preview deployment, and staging environment by hand.
- Wallet compatibility: Autosign depended on Privy's wallet connector, which required a message-signing step for authentication. That added friction to onboarding and made the flow incompatible with watch-only wallets like those in RabbyRabbyA popular multi-chain Ethereum wallet. In watch-only mode you can track balances and simulate transactions without the extension holding a signing key, so personal_sign and similar calls are unavailable..
Privy is still a great product, and we still use it for InterwovenKit's social login flow. But for Autosign, we wanted something that fit the problem more closely.
How Autosign Works
Before getting into the redesign, it helps to be precise about what Autosign is actually doing.
In addition to the delegate wallet, Autosign relies on two Cosmos SDK features: authzauthzA Cosmos SDK module that lets one account grant another permission to execute specific transaction types on its behalf., which defines what the delegate is allowed to do, and feegrantfeegrantA Cosmos SDK module that lets one account cover transaction fees for another, scoped to specific message types., which covers its gas fees. Together, they create a scoped permission model: the delegate can only execute the actions the user explicitly allows, and can only spend the user’s funds on gas for those actions.
When a user enables Autosign for an app, they send an initial transaction that grants the delegate wallet both authz permissions and feegrant coverage.
Once the grants are active, whenever the user wants to send a transaction, the following steps are followed:
- The app prepares the original messages the user wants to send.
- InterwovenKit checks that each message type is allowed for Autosign on that chain.
- Those messages are wrapped in a
MsgExec, with the derived wallet acting as the delegate. - The transaction fee is configured with the user's main wallet as the fee granter.
- The delegate wallet signs the wrapped transaction in memory and broadcasts it.
The important detail is that the delegate wallet only exists to execute a bounded set of transactions the user has already approved through authz and feegrant. Autosign is not bypassing wallet consent. It is moving repeated consent into an explicit, limited delegation model.
With that model in place, the redesign question becomes much narrower: how should that delegate wallet be created?
The Redesign
The redesign started with a narrower question: if we were no longer using an embedded wallet provider, how should we generate the delegate wallet?
There were a few obvious approaches, but each had a problem. We could generate a wallet once in the browser and store it, but a persistent private key in localStorage or IndexedDB is easier to extract through XSS, malicious extensions, or compromised dependencies. We could also generate a fresh random wallet on every session, but that would force users to recreate their grants whenever the page reloads.
What we needed was a wallet that could be recreated on demand without ever needing to be stored. That meant finding a source of input that was stable, user-authorized, and available across normal wallet flows.
Cryptographic signatures fit those constraints well. Different wallets produce different signatures for the same message, and only the user can produce the signature required to derive the delegate wallet. That gave us a way to deterministically recreate the same wallet when needed, without persisting the private key itself.
Creating the Wallet
Requesting the Signature
To do that, we ask the user to sign a fixed EIP-191EIP-191A standard for signing human-readable messages with Ethereum wallets. We chose it over EIP-712 because it has broader compatibility, especially with hardware wallets like Ledger. message.
That message includes the current app's origin, so the same user derives a different wallet for each application. This gives each app its own delegate wallet and prevents one compromised app from reusing a delegate wallet intended for another.
The signature returned by the user is 65 bytes in the standard Ethereum ECDSA layout: 32-byte r, 32-byte s, and a 1-byte recovery id v. We only use r and s, the first 64 bytes.
The final byte, v, is the recovery identifier, and different wallets encode it differently. Keeping it would make the derivation less stable across clients. By dropping v, concatenating r || s, and hashing the result with Keccak-256, we get 32 bytes of deterministic entropy that remain stable across wallets. This part of the design was inspired by dYdX v4's onboarding key derivation.
Deriving the Wallet Keys
Once we have that entropy, we feed it into the same wallet creation pipeline used by many existing wallets. BIP-39 converts the raw entropy into a mnemonic, PBKDF2 transforms that mnemonic into a seed, and SLIP-10 derives a secp256k1 private key from the seed along a standard Ethereum path. From there we compute the public key and address. Because Initia's stacks are built on the Cosmos SDK, we additionally encode the address with the chain's bech32bech32An address encoding format used by Cosmos SDK chains that includes a human-readable prefix (like 'init1' or 'cosmos1') followed by the encoded address bytes. prefix for compatibility.
Safety & Security
Deriving the wallet is only part of the problem. We also need to make sure the key material is handled safely during use. We focused on three properties:
- Memory-only key material
- Re-derivation over persistence
- Concurrency guards
Memory-Only Key Material
The derived private key is kept strictly in memory and is never written to localStorage, IndexedDB, or any other form of persistent client storage. This is a core security property: persisting key material increases exposure to malicious extensions, compromised dependencies, and XSS attacks. By keeping the key only in memory, we reduce the attack surface and avoid leaving dormant private keys behind in the browser.
For usability, we store the derived wallet address and compressed public key separately. These are required for the user interface and signing flow, but they are not sensitive: the address is public, and the public key can be exposed safely.
The only data persisted to localStorage is the expected derived address for a given user and chain. This lets us verify that on-chain grants correspond to the deterministic wallet produced by the current derivation method, and it also helps detect mismatches if the derivation scheme changes in the future.
Re-Derivation over Persistence
Because the wallet is deterministic, we can always derive it again from a fresh signature. Instead of persisting the key across sessions, we intentionally clear the in-memory wallet on page reload. If the user later wants to send a transaction, InterwovenKit prompts them to sign the same fixed message again, recreates the derived wallet, and continues the Autosign flow.
Concurrency Guards
In earlier iterations of the design, overlapping Autosign-enabled flows could finish out of order and briefly leave the wrong key in memory. In React apps, overlapping asynchronous flows can happen during repeated mounts, retries, or concurrent UI transitions. Without safeguards, multiple wallet derivation calls could resolve out of order and overwrite each other.
To prevent this, InterwovenKit deduplicates concurrent derivations for each wallet key and assigns a monotonic token to each attempt. This ensures that only the latest successful derivation can commit key material to memory. Older resolutions receive a stale token, are zeroized, and discarded.
Account Boundaries
Derived wallets are scoped to the connected user address and chain prefix. When a user switches accounts in their wallet, all Autosign wallet state is cleared immediately. Again, key and other sensitive material never remain in memory.
Conclusion
Ultimately, the redesign improved Autosign in two ways.
First, InterwovenKit became lighter and easier to adopt. Developers no longer need to install or manage Privy separately, wrap their app in Privy's provider, or configure additional credentials just to enable delegated signing.
Second, the signing model now fits Autosign more closely. Instead of relying on a hosted embedded wallet, Autosign derives a deterministic delegate wallet from a user-authorized signature, keeps the private key in memory only, and recreates it on demand when needed.
That makes Autosign cheaper to operate, easier to integrate, and more compatible with the wallets users already have, while preserving the scoped permission model that authz and feegrant provide.