
If you have heard the phrase programmable wallets thrown around — by Privy, by Coinbase, by an investor email, by us — and you are not sure what it actually means beyond "fancy smart account", this post is for you.
Stripped to its core, a programmable wallet is a crypto wallet whose behaviour is governed by code, not just by whoever happens to hold the key. You can say "this wallet may only call this contract", "this signer may only spend $50 a day", "this session expires in four hours", or "block any transaction to this address" — and the wallet enforces those rules itself. That single shift, from "key is law" to "rules are law", is what unlocks consumer wallets, in-game wallets, agent wallets, and backend wallets that are not constantly one mistake away from disaster.
This guide explains what programmable wallets are, the patterns that make them work, where the trade-offs live, and how Openfort implements them — without leading with ERC-4337 or asking you to memorise EIP numbers first.
What a programmable wallet actually is
In the original Ethereum design, a wallet is an externally owned account (EOA) controlled by one private key. There is no logic inside the wallet — anything the key signs, the wallet does. The only "rule" is "whoever holds the key, holds the money".
A programmable wallet breaks that one-to-one binding by putting the account itself inside a smart contract. The smart contract can:
- check who is asking before it executes
- enforce limits on what gets executed
- accept signatures from more than one key
- expire signers automatically
- refuse to do anything outside an allowlist
- recover access when a key is lost
Behind the scenes, this is what people mean when they say "smart account", "account abstraction" (ERC-4337), or "EIP-7702 delegation" — the standards that make programmable wallets possible on Ethereum and EVM chains. But the user-facing idea is simpler than the acronyms: a wallet that behaves like a small policy engine, not just a key holder.
That shift matters for one reason: most real-world wallet failures are not key compromises, they are over-broad signing. A session that should have only minted an NFT ended up draining tokens. An agent that should have only swapped 100 USDC moved a million. A backend service that should have only paid invoices became a withdrawal endpoint. A programmable wallet makes those mistakes mechanically impossible, not just unlikely.
Why developers reach for programmable wallets
Three product trends pushed programmable wallets out of "interesting research" and into mainstream embedded-wallet platforms.
Consumer UX needs gasless and silent signing. Asking ordinary users to approve every transaction (and pay for it in ETH) is the single biggest drop-off in onchain onboarding. With a programmable wallet you can sponsor gas through a paymaster and use short-lived session keys so the user signs once and plays.
Games need permissioned automation. A game session might generate dozens of transactions per minute. Pushing a wallet prompt for each one is product death. Game studios want a key that can mint items, claim rewards, and equip gear — but cannot transfer the user's tokens out to an address it has never seen. That is a programmable-wallet shape.
AI agents need bounded signing power. The whole point of an agent wallet is that the agent moves money. The whole point of a safe agent wallet is that it can only move the right money. Spending limits, allowlists, and time bounds are how you say "go ahead, but only this much". For a fuller treatment, see agent permissions: the need for scoped access, not private keys.
Backend wallets are the fourth, and quieter, case — server-side wallets that pay out payroll, top up balances, or rebalance treasury. Programmable controls let those wallets sign automatically without becoming a single-call withdrawal endpoint.
The three core patterns of programmable wallets
The implementations vary, but every programmable wallet you will see in production combines at least two of these three patterns.
Pattern 1 — Session keys
A session key is a short-lived, narrow-purpose signing key registered on the smart account. Instead of using the master key for routine signing, the app generates a session key, asks the user to authorise it once with the right limits, and then uses it freely within those limits until it expires.
A session key for a four-hour gaming session might say:
- valid until
now + 4 hours - may only call the
GameContract - may only call
claimReward()andpurchaseItem() - may not spend more than
0.01 ETH - may execute up to 50 operations
Inside those limits, the app signs without bothering the user. Outside those limits, the account refuses — even if a valid signature is presented. If the session key leaks, the blast radius is "those two functions, on that contract, up to those limits, for the next few hours". That is a vastly safer surface than "the master key".
Session keys are the workhorse of consumer programmable wallets, and the foundation for almost every other pattern. For a hands-on walkthrough, see how to build wallet permissions with session keys.
Pattern 2 — Scoped on-chain permissions
The session-key example above is really just one application of a broader pattern: scoped on-chain permissions. The smart account contract carries a registry of authorised keys, and each key has a permission set:
| Field | What it limits |
|---|---|
whitelist | Which contracts this key may call |
allowedSelectors | Which functions on those contracts this key may call |
ethLimit | How much ETH this key may move |
spendTokenInfo.limit | How much of a given ERC-20 token this key may move |
validUntil | When this key stops working |
period | A rolling window (daily, weekly, monthly) for the spend limit |
operationLimit | A hard count of transactions before the key is exhausted |
These rules are enforced by the smart account itself, on-chain, during transaction execution. They cannot be bypassed by a compromised backend or by a misconfigured policy layer further up the stack — the contract is the last line of defence.
This is the layer that gives programmable wallets the property normal wallets do not have: a key that is mathematically incapable of doing the wrong thing, regardless of who controls it.
Pattern 3 — Off-chain policy controls
On-chain permissions are powerful but constrained: they can only express rules you can encode in the contract. Off-chain policy controls add a flexible layer in front of signing.
A policy is a JSON-ish ruleset that says, in human terms: "for this wallet, allow sendTransaction up to 1 ETH; allow sendTransaction up to 5 ETH if the destination is the treasury address; otherwise reject". Before any key share is assembled and any signature produced, the policy engine evaluates the request against these rules. A rejected request never produces a signature at all — no gas is burnt, no signature is leaked.
_21{_21 "scope": "project",_21 "description": "Default spending controls",_21 "rules": [_21 {_21 "action": "accept",_21 "operation": "sendTransaction",_21 "criteria": [_21 { "type": "value", "value": "1000000000000000000", "operator": "<=" }_21 ]_21 },_21 {_21 "action": "accept",_21 "operation": "sendTransaction",_21 "criteria": [_21 { "type": "value", "value": "5000000000000000000", "operator": "<=" },_21 { "type": "address", "addresses": ["0xTreasuryAddress"], "operator": "in" }_21 ]_21 }_21 ]_21}
Off-chain policies are best at:
- address blocklists (e.g. block known phishing or mixer addresses)
- velocity / rate limits (e.g. no more than 10 outbound transactions per hour)
- business-logic gates (e.g. only sign if the user is in good standing)
- anomaly detection (e.g. flag a transaction that is 100× a wallet's normal size)
- multi-party approval (e.g. require human approval above a threshold)
They are weaker than on-chain rules in one sense: only the party running the policy engine can prove the rule was applied. That is why most production systems run both layers together — see the section on combining the two below.
Openfort's backend-wallet policy controls are a textbook example of the off-chain pattern, applied to server-side wallets. Project-level policies set the baseline, account-level policies override or extend, and a transaction is only signed if both layers accept it.
Combining the layers: programmable wallets in production
A real programmable wallet rarely uses just one pattern. The strongest setups stack at least two:
- Off-chain policy evaluates the request before any key material is touched
- Session key signs within tight on-chain limits if the policy accepts
- On-chain permissions enforce the final rules at the contract level
_13Request_13 │_13 ▼_13Off-chain policy engine ── reject ──► Abort (no signature)_13 │ accept_13 ▼_13Session-key signing within scoped permissions_13 │_13 ▼_13Smart account on-chain rules ── reject ──► Revert_13 │ accept_13 ▼_13Transaction executes
This stacking is what makes the model robust. A bug in the off-chain layer is caught by the on-chain layer. A misconfigured on-chain limit is mitigated by the policy engine. A leaked session key is constrained by both. The whole point of programmable wallets is that no single layer is "the security".
Openfort wires this together by default. Backend wallets carry both project- and account-level policy controls. Smart accounts behind them carry session keys, contract allowlists, spending caps, and time bounds. Embedded wallets — wallets created silently for end-users — use the same smart-account contract under the hood. The patterns share the same primitives even when the UX is wildly different.
Trade-offs to weigh before adopting programmable wallets
Programmable wallets are not a free upgrade. The honest trade-offs are:
- Configuration matters. A session key with no spending limit is just a hot key with an expiry. Programmability only buys safety if the rules are actually narrow. A common failure mode is to migrate to a smart account and configure session keys with
ANY_TARGET— which preserves all the operational complexity and removes none of the risk. - Gas overhead is real but small. Smart accounts execute contract logic on every transaction, which costs a bit more gas than an EOA. On L2s this is negligible; on mainnet it is measurable but usually invisible to end-users behind gas sponsorship.
- Contracts must be audited. A buggy programmable wallet is worse than a simple EOA. Use battle-tested implementations and check for audits. (Openfort's wallet contracts, OpenSigner, and account delegation have been audited by Certik, Omniscia, Cure 53, and Quantstamp.)
- Recovery is now a design problem. A normal EOA's "recovery" is "remember your seed phrase". A programmable wallet can offer passkey backup, guardian sets, time-locked recovery — but you have to choose, configure, and test them. We covered this end-to-end in the crypto wallet security guide.
- Cross-chain identity needs care. Smart accounts deploy on-chain. Making sure the same address resolves on every chain you support requires deterministic deployment patterns. Embedded wallets that hide this from the user are good; ignoring it is bad.
None of these are reasons not to use programmable wallets. They are reasons to pick a provider that has worked through them, or to budget the engineering time to do it yourself.
How Openfort builds programmable wallets
Openfort's programmable wallets live across three product surfaces. The mechanics are the same; the UX differs.
- Embedded wallets: smart-account wallets created silently when an end-user signs up with email, social, or a passkey. Session keys, gas sponsorship, and scoped permissions ship out of the box.
- Backend wallets: server-side wallets for payroll, payments, agents, treasury. These wallets carry the off-chain policy controls described above — project-level and account-level rules, evaluated inside a trusted execution environment before any key share is assembled.
- Global wallets: cross-app smart accounts that follow a user across multiple apps in an ecosystem.
All three sit behind OpenSigner, Openfort's open-source signer. Private keys are split using Shamir Secret Sharing across the user's device, their login, and an encrypted recovery share. Keys are recombined only briefly, inside an isolated environment, at signing time. The policy layer evaluates before keys are ever assembled — which means a rejected policy request truly never produces a signature.
A worked example of the on-chain permission side is in how to build wallet permissions with session keys. The agent-wallet variant — narrower scopes, tighter limits, automated kill switches — is covered in agent permissions: the need for scoped access, not private keys.
Programmable wallets vs the alternatives
If you are choosing between "programmable wallets" and other approaches, here is the rough comparison most teams end up making.
| Capability | Programmable wallet (Openfort) | EOA + application logic | MPC wallet without smart account |
|---|---|---|---|
| Pre-signing rejection | Yes (off-chain policy + on-chain rules) | Custom-built per app | Limited to provider's middleware |
| On-chain verifiability | Yes — anyone can verify rules on-chain | No — rules live in your app | No — provider runs the rules |
| Composability across protocols | Yes — rules apply wherever the account is used | No — rules only apply through your app | No — rules only apply through provider |
| Recovery without seed phrase | Yes — passkey backup, guardians, social recovery | No — seed phrase or app-managed | Yes — but provider-dependent |
| Per-key spending limits | Yes — enforced on-chain | Custom per app | Provider-dependent |
| Gas sponsorship | Yes — paymaster integrations | Manual | Manual |
The summary is: programmable wallets are the only architecture where the rules travel with the account. An app-layer rule is only as portable as your app. A provider-layer rule is only as portable as your provider. A smart-account rule is portable everywhere the account is.
Try programmable wallets with Openfort
If you want to ship a programmable wallet today, the fastest path is:
- Pick the surface. Embedded wallets for end-users; backend wallets for server-side; agents get backend wallets plus extra-tight scopes.
- Set a project-level policy (for backend wallets). Define the defaults: max spend per transaction, allowed addresses, blocked operations.
- Configure on-chain permissions on the smart account: session keys, contract allowlists, function allowlists, period-based spend caps.
- Wire up gas sponsorship with a paymaster so users do not need to hold native tokens.
- Test recovery. Walk through losing access and recovering, before any real money is involved.
The Openfort docs cover all five steps end-to-end. The embedded wallet product page is the right starting point for consumer apps; the wallet-as-a-service page covers the backend and ecosystem cases.
Programmable wallets are not magic — they are an architecture for making wallets safer to delegate, easier to recover, and harder to misuse. Choose one that gets the layers right, and you stop having to argue about whether the key has been "leaked yet". The key cannot be used outside its rules in the first place.
