Anti-Patterns
Common mistakes when deploying agent wallets, designing governance, or operating ULedger. Each entry states what not to do, why it is wrong, and what to do instead.
Wallet and Capability Anti-Patterns
1. Sharing a Wallet Between Multiple Agents
What: Two or more autonomous processes sign transactions using the same .ukey file and wallet address.
Why it's wrong: The audit trail cannot distinguish between the processes. Every transaction appears to come from the same actor. The on-chain record is meaningless for accountability purposes; the core value of agent wallets is lost. Additionally, concurrent signing from multiple processes introduces nonce and sequencing race conditions.
Do instead: Generate one wallet per autonomous process. Wallets are cheap. One agent = one wallet = one traceable address.
2. Wildcard Capabilities ("action": "*", "resource": "*")
What: Assigning a capability with action and resource both set to "*", giving the agent unrestricted protocol access.
Why it's wrong: This is equivalent to giving the agent a full admin credential. It defeats the purpose of the capability system. If the agent is compromised or malfunctions, it can take any action on the chain: including transferring tokens, creating wallets, or invoking unrelated contracts.
Do instead: Enumerate the minimum set of actions and resources the agent needs. If the agent only invokes one contract, scope to "action": "contract:invoke", "resource": "<specific-contract-address>". If it transfers tokens, scope to the specific token address and set a maxAmount.
3. No Expiry on Agent Capabilities (notAfter: 0)
What: Creating capabilities with notAfter: 0 (no block-height expiry).
Why it's wrong: An agent wallet with no expiry capabilities persists indefinitely. If the agent is abandoned, forgotten, or compromised, it retains protocol access forever. Auditors will find stale agent wallets with active capabilities years after the agent was last used.
Do instead: Always set notAfter to a reasonable block horizon. Implement a capability renewal process (see Integrating External AI Agents) that renews before expiry. Treat capability renewal as a liveness check; if the renewal process breaks, capabilities expire and the agent is automatically silenced.
4. Skipping Recovery Configuration for Agent Entities
What: Deploying agent entities without configuring recovery wallets.
Why it's wrong: If the parent (human) wallet's private key is lost, there is no recovery path. The agent entity is permanently orphaned; it cannot be transferred, re-scoped, or disabled through normal channels. An orphaned agent with unexpired capabilities continues operating with no human control.
Do instead: Configure CONFIGURE_ENTITY_RECOVERY for every entity that holds agents. Set a threshold of at least 2 out of 3 recovery wallets. Recovery wallets should be held by different people than the owner wallet: geographic and organizational separation.
5. Storing Agent Private Keys in Environment Variables or Source Control
What: Embedding .ukey file content in environment variables, Dockerfiles, .env files, or committing key files to git.
Why it's wrong: Environment variables are visible to any process in the container. Source control is forever. A leaked agent key means an attacker can submit transactions attributed to the agent; those transactions are on-chain, permanent, and indistinguishable from legitimate agent actions.
Do instead: Use a secrets manager (HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets). Mount key files at runtime into a non-world-readable path. Never print key bytes in logs. Treat .ukey files like private SSH keys.
6. Checking Capability Expiry Before Submission, Not After
What: The application checks notAfter > currentBlock before submitting a transaction and assumes approval is guaranteed.
Why it's wrong: Capability expiry is checked at execution time (block processing), not at submission time. A transaction submitted before the capability expires may not be included in a block until after expiry. By the time the block is processed, the capability is expired and the transaction fails; gas is consumed and a rejection is recorded on-chain.
Do instead: Build expiry buffer into capability notAfter values. Design notAfter to be well beyond the expected last use. Monitor capability status regularly and renew proactively, not reactively.
Entity Hierarchy Anti-Patterns
7. Giving Agent Wallets Admin-Level Entity Permissions
What: Linking an agent wallet to its entity with ManageChildren: true, ManageWallets: true, ManageAccessList: true, or TransferOwnership: true.
Why it's wrong: These permissions allow the agent to restructure its own governance: create sub-entities, modify who can interact with it, or transfer itself to a new owner. A compromised agent could use these permissions to entrench itself or sever human oversight.
Do instead: Agent wallets should have ManageMetadata: true (so they can update their own operational metadata) and nothing else, unless there is a specific, justified need.
8. Not Enabling Cascade Disable
What: Disabling an individual agent wallet without disabling its parent when you intend to disable all agents in a subtree.
Why it's wrong: Other child wallets of the same parent are unaffected. If a parent wallet has 10 agent children and one is compromised, disabling only the compromised wallet leaves 9 others running. In an incident response scenario, the correct lever is to disable the parent; this cascades via BFS to all satellites atomically.
Do instead: Understand the wallet hierarchy before an incident. Know which wallet is the parent of each agent subtree. In incident response, disable the parent to cascade. For targeted removal, disable the specific child wallet.
9. Using PERSONAL Entities for Agents
What: Creating an AI agent as a PERSONAL entity instead of an AGENT entity.
Why it's wrong: PERSONAL entities can have children (SERVICE, DEVICE, AGENT). An agent that is modeled as PERSONAL can create sub-entities, giving it organizational governance power it should not have. It also misleads auditors who read the entity type as indicating a human subject.
Do instead: Model autonomous processes as AGENT entities (leaf type, no children). Model persistent automated services as SERVICE entities. Use PERSONAL only for human users.
Contract Design Anti-Patterns
10. No Owner-Only Emergency Functions
What: Deploying a contract with no functions that only the owner can call.
Why it's wrong: If the contract logic has a bug, or if the system enters an unexpected state, there is no administrative lever to pause, reset, or correct the contract without deploying an upgrade. An upgrade requires the owner wallet; if there is no on-chain emergency pause, the buggy contract continues executing during the upgrade process.
Do instead: Include at minimum a pause() function callable only by the contract owner. When paused, all non-owner functions revert immediately. This gives the owner a way to halt the system while an upgrade is prepared.
11. Storing Sensitive Data in Contract Storage
What: Writing PII, credentials, private keys, or other sensitive data to contract storage via set_storage().
Why it's wrong: Contract storage is on-chain. It is visible to all node operators, auditors, and anyone with access to the chain's data. The STARK proof layer makes this data permanently and publicly provable, which is the opposite of what you want for sensitive data.
Do instead: Store only hashes, commitments, or references to sensitive data. Keep the actual data off-chain in a private store. Record the hash on-chain so that the reference is provable, but the data itself is not exposed.
12. Conflating Contract Access Control with Protocol Capabilities
What: Relying only on contract-internal get_caller() checks to gate agent access, without setting wallet capabilities.
Why it's wrong: If the contract is upgraded, rolled back, or has a bug in its access control, the wallet can submit transactions that bypass the intended gates. Protocol-level capabilities are enforced by the node; they cannot be bypassed by contract logic.
Do instead: Use both: wallet capabilities gate whether the wallet can invoke the contract at the protocol level; contract logic gates which functions the caller can execute at the application level. This is defense in depth.
Operational Anti-Patterns
13. Single Recovery Wallet with Threshold 1
What: Configuring recoveryWallets: [one-wallet] with threshold: 1.
Why it's wrong: A single-approval recovery requires only one party to execute a takeover. If that recovery wallet is compromised, or if a social engineering attack convinces that one person to approve a malicious recovery, the entity is seized. There is no second check.
Do instead: Use a minimum threshold of 2 out of 3 recovery wallets. Recovery wallets should be held by different people, different organizations, or different physical locations. For high-value entities, consider 3 of 5.
14. Not Monitoring Checkpoint Liveness
What: Deploying a Helix cluster and not running the independent auditor.
Why it's wrong: The STARK proof chain is only as valuable as the monitoring of it. If the Helix sidecar crashes, checkpoint generation stops, and the audit trail's tamper-evidence guarantee degrades silently; no one knows the proofs have stopped until an auditor needs them.
Do instead: Run the independent auditor sidecar continuously. Alert on checkpoint height lag (no new checkpoint within N expected epochs). Treat checkpoint liveness as a first-class operational metric, not an afterthought.
15. Rebuilding Historical State from Logs
What: Treating the node's application logs as the audit trail, and ignoring the on-chain transaction history.
Why it's wrong: Application logs are mutable (any node operator can alter them), siloed (each node has its own log), and not cryptographically bound to the consensus output. They are operational diagnostics, not an audit record.
Do instead: The on-chain transaction history (verified by STARK checkpoints) is the audit trail. Build compliance and audit tooling against the chain API and checkpoint verification, not against log files.