TL;DR
A wallet policy engine decides whether a wallet may sign, before it signs. The five platforms differ most on two things buyers rarely check: where the rule is enforced, and whether the audit trail can be exported. Openfort enforces API-side before key shares are assembled and again on-chain in the smart account, fails closed, and exposes transaction records through GET /v2/transactions with HMAC-signed webhooks. Privy and Turnkey enforce API-side inside a secure enclave; Turnkey is the only one of the five documenting a cryptographically tamper-proof activity log. Fireblocks has the strongest amount controls, with cumulative limits over a time period and approval quorums. Crossmint enforces scopes API-side before broadcast, not on-chain. Spending policies, transaction limits and the signed audit trail are the wallet-layer primitives compliance and monitoring programs are built on — none of these five is a KYC/AML product.
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 five platforms — Openfort, Privy, Turnkey, Fireblocks and Crossmint — actually implement the controls, on documented evidence.
What a wallet policy engine is
A wallet policy engine is a rule-based authorization control that decides whether a wallet may perform a signing operation, evaluated on every request before any signature is produced. A policy holds an ordered list of rules; each rule pairs an operation with an outcome — allow or deny — and a set of conditions that must match for the rule to apply.
That is the whole mechanism. Everything else in this post is a question about one of four properties:
- Which conditions can a rule express? Value caps, recipient addresses, contract and function names, networks, time.
- Where is the rule enforced? In your application code, in the vendor's signing path, or on-chain in the account contract.
- What happens when nothing matches? Fail-open permits it; fail-closed rejects it.
- What does the decision leave behind? A transaction record, a policy decision, an event your monitoring stack can consume.
The compliance vocabulary gap
Buyers arrive at this category using two different vocabularies for the same machinery. Engineers ask for spending controls, transaction limits and session keys. Risk and finance teams ask for transaction monitoring, audit trails and compliance reporting. They are asking about the same layer.
Spending policies, transaction limits and the signed audit trail are the wallet-layer primitives that compliance and monitoring programs are built on. They are not a KYC/AML product.
That distinction is worth stating precisely, because vendors in this category routinely blur it. A policy engine decides whether a given signing request is permitted. It does not verify customer identity, and it does not screen counterparties against sanctions lists. Every platform below integrates with an identity-verification provider and a transaction-screening provider for those functions rather than providing them. If a wallet vendor tells you it "handles compliance", ask which of the three layers it means — identity, screening, or wallet controls. The full separation is covered in the best compliance and security controls for stablecoin wallet infrastructure, and the enterprise agent-wallet version of the same review is in AI agent wallet compliance: custody and control requirements.
Quick comparison: five wallet policy engines
Facts below come from each vendor's own documentation, linked inline. A blank cell means the cited documentation does not establish the capability — not that the vendor lacks it.
| Platform | Policy model | Where rules are enforced | Fails closed by default |
|---|---|---|---|
| Openfort | JSON rules: an action (accept / reject), one operation, and criteria combined with AND. Policies are ordered by priority and the first matching rule wins | API-side before key shares are assembled, and on-chain in the smart account for session-key permissions | Yes — once a project has one policy, any operation matching no rule is rejected |
| Privy | Policies contain rules, rules contain boolean conditions evaluated against the incoming RPC request. Chain-specific | API-side, inside Privy's secure enclave, before signing or key export | Yes once a policy is attached — the policy must include a rule for every method the wallet intends to use |
| Turnkey | JSON policies with an effect (ALLOW / DENY), a consensus clause and a condition, written in Turnkey's policy language | API-side, in the secure enclave, before a signature is produced | Yes — almost all actions are implicitly denied by default |
| Fireblocks | Transaction Authorization Policy rules: an action (ALLOW / BLOCK / 2-TIER), asset, source and destination, amount, transaction type and authorization groups | Platform-side, in the transaction approval layer, before signing | |
| Crossmint | Signer scopes — one scope type, transfer, applying to a single token on a single chain | Crossmint API-side, checked before the transaction is broadcast on-chain, not by contract logic | No — a signer with no scopes is unrestricted |
The line that matters most in that table is the third column. A limit enforced in your application code is only enforced on the paths your application controls: a second integration route, a compromised service, or a direct API call bypasses it entirely. Every platform here moved enforcement into the signing path, which is the right architecture. Openfort is the only one of the five that also enforces a subset of rules on-chain, where the account contract checks them during execution and the rule holds even if the signing service is bypassed.
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 |
validAfter | When this key starts working |
validUntil | When this key stops working |
limit | A hard count of operations before the key is exhausted |
Those are the fields Openfort's session key API documents. Smart accounts can express finer-grained permissions than this — per-key token spend caps and function-selector allowlists among them — but what a given account contract enforces depends on its implementation, so treat the contract, not the marketing page, as the source of truth. Openfort also supports the EIP-7715 wallet_grantPermissions method, which grants a signer an expiry and a list of contract-call permissions scoped to specific addresses.
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.
Can you ship multi-chain wallets with custom policy controls in under a week?
This gets asked often enough to answer directly. Yes, for the common case, and here is the honest breakdown rather than a marketing number.
Realistic in a week: wallet creation across EVM chains and Solana, per-transaction value caps in native tokens and ERC-20s, address allowlists and denylists, contract allowlists with function-level filters, session keys with expiry, and gas sponsorship policies. These are configuration, not engineering. The recipes-hub has runnable examples, and a policy-scoped wallet takes an afternoon end to end.
Not realistic in a week: a custom validation hook with novel on-chain logic that needs an audit, integration with an existing internal approval system, or anything requiring a bespoke enforcement contract. Audits alone run longer than a week, and shipping unaudited validation logic that guards money is how teams end up in an incident review.
The step most teams underestimate is not the wallet. It is deciding what the policy should be. Writing "50 USDC per day against these two contracts" takes ten minutes; agreeing internally on the number, who can raise it, and what happens when it is hit takes longer than the integration.
If a vendor answers this question with an unqualified yes, ask which of the three categories above they mean.
What auditors and procurement will ask
Policy engines end up in security reviews, so the questions arrive in a predictable order.
| Question | What a good answer looks like |
|---|---|
| Where is the policy enforced? | On-chain, in a TEE, or in a backend service. Say which, because the trust assumptions differ. |
| Can the operator change the policy silently? | On-chain policy changes are transactions. Backend policy changes are database writes. |
| What is in the audit log, and for how long? | Which key signed, under which policy version, at what time, with what outcome. |
| How fast can authority be revoked? | On-chain revocation is one transaction, effective at the next validation. |
| SOC 2 Type II? | Ask for the report, not the badge. |
| ISO 27001? | Ask for the certificate and its scope, which is often narrower than the company. |
| Independent penetration testing? | Ask who performed it and when. |
The two rows people forget are audit-log retention and policy versioning. A log that records the transaction but not the policy in force at the time cannot answer the only question that matters after an incident: was this supposed to be possible?
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.
- Cross-app wallets: 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.
Detailed feature comparison
One row per control, one column per platform. Cells cite the documented field or feature name. A blank cell means the cited documentation does not establish the capability; it is not a claim that the vendor lacks it.
| Control | Openfort | Privy | Turnkey | Fireblocks | Crossmint |
|---|---|---|---|---|---|
| Per-transaction value cap | ethValue, solValue, splValue with <=, <, >=, > | Numeric operators lt, lte, gt, gte, eq on transaction value | eth.tx.value in wei; Solana Transfer.amount in lamports | amount with amountCurrency (USD, EURO, NATIVE) and amountScope: SINGLE_TX | Per-token amount in display units, converted using the token's on-chain decimals() |
| Cumulative / per-period spend limit | Not aggregated by the policy engine | Cumulative values over a time window via the reference field source | amountScope: TIMEFRAME with periodSec and an amountAggregation object | Optional interval in seconds resets the limit | |
| Recipient allowlist | evmAddress with in, up to 100 addresses per criterion | Allowlists of transfer recipients; in up to 100 values, in_condition_set beyond | eth.tx.to comparison | Destination (dst) matching, including whitelisted addresses | recipients array; empty or omitted allows any recipient |
| Recipient denylist | evmAddress with not in | Denylists of transfer recipients | DENY effect; explicit deny overrides allow | BLOCK action | |
| Contract allowlist | evmAddress; Solana programId and mintAddress | Allowlists and denylists of smart contracts and programs | Contract address conditions | Contract calls gated as a type via transactionType: CONTRACT_CALL | |
| Function / method restriction | evmData matches calldata against an ABI by functionName, with constraints on decoded args by name or position | ethereum_calldata field source decodes function calls, allowing rules on function names or parameters | function_name and function_signature with an uploaded ABI; Solana instruction_name via IDL | ||
| Network restriction | evmNetwork with a chainIds list | Allowlists and denylists of networks | Chain ID conditions | asset field | Scope applies per token per chain |
| Time bounds | Session-key validAfter / validUntil, enforced on-chain | Time-bound signers; current_unix_timestamp via the system field source | Top-level time field using Timestamp() or CronSpan(); expiring API keys via expirationSeconds, default 900s | Signer-level expiresAt (ISO 8601) denies all transactions after that moment | |
| Session / scoped signers | Session keys registered on the smart account with a contract whitelist, a use limit, and single-call revocation | Session signers scoped by attaching a policyIds array at signer-add time | Delegated access via a scoped API-only user inside the end-user's sub-organization | Delegated signers constrained by scopes | |
| Approval threshold / quorum | No — the engine's actions are accept and reject only | Key quorums for authorization keys; approval thresholds inside policy rules not documented | consensus clause, e.g. approvers.count() >= 2 | authorizationGroups with a threshold (th), AND/OR logic, and the 2-TIER action | |
| Pre-flight rule test | policies.evaluate() returns the decision without performing the operation | Transaction simulation runs before policy evaluation for sign-and-broadcast operations | Violations rejected at validation time | ||
| Tamper-evident audit log | Not claimed — transaction records plus HMAC-signed webhook delivery | Every sensitive action logged in a cryptographically verifiable, tamper-proof format, tied to the initiating user's authenticator | Audit logs described in the Fireblocks Microsoft Marketplace listing; tamper-evidence not verified | ||
| Audit-trail retrieval | GET /v2/transactions, filtered by account, user, wallet, chain, fee sponsorship or status, with expand=timeline and expand=logs | No documented bulk export endpoint; dashboard Activity logs cover configuration changes, Webhooks history covers outbound messages; REST API for transaction status | Activities tab in the UI and the Get activity query endpoint; no bulk export endpoint documented | Fireblocks advertises "complete policy documentation and transaction authorization history" and "full audit trails of all compliance decisions" on its governance and policies page; no export mechanism documented | |
| Real-time monitoring | Webhooks for transaction and balance-threshold events, signed with an HMAC-SHA256 digest in the openfort-signature header; dashboard records every delivery attempt | Svix-signed webhooks including transaction.confirmed, transaction.failed, wallet.funds_deposited, wallet.private_key_export. Production webhooks require the Enterprise plan | Webhooks delivering ACTIVITY_UPDATES with the full activity object, plus balances:confirmed and balances:finalized | Transaction events and transaction alerts such as transaction.alert.stuck; Webhooks v1 is deprecated from 15 June 2026 in favour of V2 | Wallet webhooks covering transfers and signer exports |
| SOC 2 Type II | No | Yes | Yes | Yes | Yes |
| Published independent security audits | Five, dated: CertiK (Dec 2023), Cure53 (Sep 2024), Omniscia (Dec 2024), Quantstamp (Sep 2025 and Oct 2025) | ||||
| Open-source signer | OpenSigner |
Two rows deserve comment because they cut against the vendor writing this post.
Openfort does not hold SOC 2 Type II. Privy, Turnkey, Fireblocks and Crossmint do. If your procurement process requires that attestation, that is a real and current gap, and no quantity of code audits substitutes for it — a SOC 2 report audits company controls over a period, while a code audit audits the signer. They are different artefacts answering different questions and should never share a column. What Openfort publishes instead is five dated third-party audits and the signer source code, which is the evidence a reviewer can check directly rather than take on trust.
Turnkey has the strongest audit-log story of the five. A cryptographically verifiable, tamper-proof log tied to each user's authenticator is a genuinely stronger artefact than a queryable transaction table, and if tamper-evidence is the control your auditor is testing, Turnkey documents it and the others do not.
Fireblocks has the strongest amount controls. Cumulative limits across a time window, denominated in fiat, combined with approval quorums, is the most complete expression of "no more than $500,000 per 24 hours, and above that require two approvers" available in this comparison. Openfort's policy engine caps individual transactions and does not aggregate across them.
What the audit trail records, and how you get it out
"Audit trail" is used loosely enough to be worth pinning down. The question a reviewer is really asking is whether, months later, you can reconstruct what was requested, which rule decided it, what the decision was, and what happened on-chain.
For Openfort, each transaction record carries:
- the transaction ID, creation and update timestamps, and status
- the chain ID, the account that executed it, the wallet that owns that account, and the fee sponsorship that paid for gas
- every call the transaction executes — destination, value, and either raw calldata or the contract and function name with its decoded arguments
- how it executed: an ERC-4337 user operation, including EIP-7702 delegated accounts, or a plain transaction
- once terminal, a receipt with the transaction hash, block number, gas used, gas fee, cost in USD, and the revert reason when it failed
Policy decisions are a separate record: policies.evaluate() returns whether the operation was allowed, the reason, and the IDs of the policy and rule that matched.
You retrieve it three ways. GET /v2/transactions returns records most recent first, filtered by account, user, wallet, chain, fee sponsorship or status, with expand=timeline for the ordered lifecycle history and expand=logs for receipt event logs. Webhooks push transaction and balance-threshold events to your backend as HTTPS POSTs carrying an HMAC-SHA256 digest of the raw body in the openfort-signature header, so the receiving system can verify the payload was not altered in transit. The dashboard records every delivery attempt with its status, response code and payload.
The gap to be honest about: the transaction record and the policy decision are two separate artefacts. A single record that binds the policy version in force to the transaction it authorized — the thing an incident review actually wants — is something you assemble by correlating them on the account and timestamp, not something the API returns in one call.
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 with a contract whitelist, a validity window, and an operation count.
- Wire up gas sponsorship with a paymaster so users do not need to hold native tokens.
- Wire the audit trail into your monitoring stack before launch, not after an incident. Register a webhook endpoint, verify the
openfort-signatureHMAC on every delivery, and store the transaction records where your reporting queries can reach them. - Test recovery. Walk through losing access and recovering, before any real money is involved.
The Openfort docs cover these steps end-to-end, and the policies documentation is the full rule reference. The wallet automation page lists the enforceable control types and what the audit trail records. 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.
One last time, because it is the sentence buyers most often get wrong in both directions: spending policies, transaction limits and the signed audit trail are the wallet-layer primitives that compliance and monitoring programs are built on. They are not a KYC/AML product. Build the wallet controls here, and connect identity verification and transaction screening from providers that specialise in them.
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.







