Skip to content

Best Practices for Policies in Fordefi

This page recommends how to structure your transaction policy. It assumes you know how the policy engine evaluates rules and which conditions and actions a rule can use.

Set a strict default action

The default rule is the policy's catch-all: it always sits at the bottom, and only its action can be changed. Set it to Block, or to Require Approval with an approval threshold that prevents any single account from authorizing a transaction alone.

With a restrictive default, your custom rules form a whitelist: you explicitly allow what you trust, and everything else is stopped. With a permissive default, your custom rules must form a blacklist: nothing is stopped unless you remembered to write a rule for it — a much harder posture to get right. A permissive default may be reasonable in an organization that you use for testing or that holds negligible value, but make that choice deliberate, not accidental.

Order rules from most specific to most general

The policy applies the first rule that matches, so ordering resolves overlaps between rules. Start with the narrowest, highest-priority rules, then move toward broader rules.

A typical policy is ordered like this:

  1. Specific Block rules: Narrow prohibitions that must override matching Allow rules.
  2. Specific Allow rules: Trusted, low-risk flows, so routine operations stay fast.
  3. Guardrails: Broader Require Approval rules that catch everything sensitive.

Below these tiers, the strict default rule remains fixed at the bottom as the fallback.

Targeted Allow rules might include:

  • Tightly scoped DeFi interactions: A specific trusted contract through the DApp directory, with an amount cap — for example, swaps on Uniswap V3.
  • Transfers to known counterparties: Whitelist recipients through address book groups — for example, recurring transfers to your exchange deposit addresses, or between your own vaults.

A common tiering by amount is to auto-allow small amounts (for example, ≤ $1,000 per transaction, with a daily limit), require approval for medium amounts (for example, ≤ $1M), and let anything larger fall through to the strict default rule.

Cap the per-transaction and periodic amounts

An Allow rule without an amount condition can authorize unbounded value from every vault it applies to. Unless those vaults are themselves low-value, cap the rule with a per-transaction amount condition. A per-transaction cap alone is still not enough: without a periodic (for example, daily) limit, many small in-policy transactions can add up to the same drain.

For contract calls, the per-transaction amount condition can use the outgoing or the net amount — everything leaving the vault, or outgoing minus what comes back. Use net for tightly scoped swap-like flows in which assets return to the vault. Use outgoing when you want to bound the gross value placed at risk.

Periodic amount conditions always use net amount. Configure their scope deliberately: Count matching transactions gives each rule its own counter, while Count all transactions includes activity matched by other rules. Choose All users for a limit shared across the organization, or Per user for a separate limit per initiator. Periods are fixed calendar periods in UTC, not rolling windows.

On contract calls, the Amount and Asset conditions are derived from transaction simulation and can become unevaluable when simulation fails — see rule conditions and actions for how the engine handles this. It is one more reason to keep the default rule strict.

Constrain token allowances like transfers

An allowance moves no funds immediately, but it lets the spender transfer the approved amount later. Granting an allowance to a trusted DApp contract is routine; granting one to an unknown spender is as risky as sending it the funds. Fordefi treats allowances as first-class: they count toward per-transaction and periodic spending limits, the spender is matched as the recipient, and Permit and Permit2 messages are classified as allowance transactions even though they are signed off-chain.

So constrain allowances the way you constrain transfers: whitelist trusted spenders through the DApp directory, and cap amounts — even for trusted DApps, because if the DApp's contract is ever compromised, it can spend everything it was approved for. An unlimited allowance does not satisfy a finite per-transaction cap, so capped Allow rules and a strict default make it fall through to scrutiny. Periodic-limit accounting handles unlimited allowances specially and does not add their unlimited value to the running total.

Constrain contract calls like transfers

There is no strong security distinction between the two: some DEX contracts accept a custom withdrawal address and effectively function as transfers. Blocking transfers — or requiring a high approval threshold only on transfers — is not enough to prevent exfiltration, because a contract call to the right (or wrong) DEX moves funds out just as effectively. Apply recipient, amount, and approval constraints to contract calls too.

For a contract call, the Recipient condition matches the contract being called, not an address that ultimately receives assets. If a method accepts a receiver, beneficiary, or withdrawal address, constrain that parameter with an ABI condition where possible. A recipient allowlist by itself cannot guarantee that proceeds return to the origin vault.

This is also why the DeFi Allow rules above must keep their amount caps: even a trusted DEX can be instructed to send the swap proceeds elsewhere, and the amount cap bounds the damage.

For swap flows, prefer Fordefi's in-app swap: it is its own transaction type, supporting recipient, asset, and amount conditions and encapsulating the required allowance — so you can allow swaps specifically, without opening up generic contract calls to DEX contracts.

Scope rules to vault groups organized by use case

Structure your vaults around use cases, then write least-privilege rules per vault or vault group. For example, give treasury operations and a trading desk their own vault groups, and write each flow — treasury transfers, interaction with DeFi protocols — its own rules with its own recipient list, asset list, and amount cap, combined with user-role permissions. Avoid a single rule that covers all vaults and all chains: it cannot express least privilege.

Give API users their own, tighter rules

An API user is a credential that initiates transactions programmatically, and a leaked credential or a compromised bot will initiate exactly what your policy allows it to. Use the Initiator condition to give API users their own rules, scoped to exactly what the integration needs: only the vaults they operate, whitelisted recipients, amount caps, and periodic limits sized to their actual activity.

Reference groups instead of individual entries

Wherever possible, have rules refer to user groups, vault groups, DApp groups, and address book groups rather than listing individual users, vaults, DApps, or addresses. This keeps the policy itself stable: onboarding a new trader, vault, DApp, or counterparty becomes an edit to a group, which is easier to make and easier to review than a change to the policy rules.

Give message signing its own rules

Message signing is a distinct attack surface. Write explicit Message rules for the Personal Message, Typed Data, and non-EVM message flows your users actually need, and let the default rule catch the rest.

Do not rely on recipient or amount conditions to constrain every message type. Personal Messages and non-EVM messages have no recipient and are treated as amount zero. Typed Data has an unknown amount, so a rule with an amount condition will not match it. For Typed Data, constrain the verifyingContract and the EVM Typed Message domain and object type where possible.

Review the policy periodically

Keep rules clear and simple — avoid generic or overly complicated conditions — and audit the policy periodically to tune controls and avoid drift as your operations change.