---
title: "Policies"
description: "Set programmable guardrails that automatically allow, block, or route sensitive actions through normal approval in your workspace."
url: https://docs.fystack.io/product/policies
last_updated: 2026-10-15
---

> ## Documentation index
> Fetch the complete documentation index at: https://docs.fystack.io/llms.txt
> Use it to discover all available pages before exploring further.

# Policies

Set programmable guardrails that automatically allow, block, or route sensitive actions through normal approval in your workspace. Policies evaluate every withdrawal and smart contract call against rules you define, so risky activity is stopped before it ever reaches the blockchain.

## Overview

A **policy** is a set of rules that runs automatically whenever someone initiates a guarded action, such as requesting a withdrawal or calling a smart contract. Each rule describes a condition (for example, "withdrawal value over $10,000 to a non-whitelisted address") and an effect:

- **Deny**: block the action outright
- **Allow**: let the action skip the approval queue and proceed directly

Policies act as an always-on layer of defense that complements your signer approvals. Instead of relying on every approver to remember your treasury rules, you encode those rules once and let Fystack enforce them on every transaction.

_Image: Policy List_

## How Policies Are Evaluated

When a guarded action runs, Fystack checks it against every active policy that targets the relevant wallet and applies a custody-safe decision model:

1. **Deny always wins**: if any rule matches with a **Deny** effect, the action is blocked, even if another rule would allow it.
2. **Allow bypasses approval**: if a rule matches with an **Allow** effect and nothing denies, the action skips the approval queue and proceeds.
3. **No match**: if no rule matches, the action continues through your normal approval flow.

> **Note:** Policy evaluation is **fail-closed**. If a policy cannot be evaluated for any
> reason, the action is denied rather than allowed through. This guarantees a
> misconfiguration or system issue can never silently weaken your controls.

## Trigger Actions

Each rule applies to one trigger action, the type of operation it guards:

| Trigger | Guards |
| --- | --- |
| Withdrawal | Evaluated when a withdrawal is requested. Rules can reference the amount, USD value, asset, destination address, whitelist status, address age, and recent wallet, workspace, and network activity. |
| Contract Call | Evaluated when a smart contract method is called. Rules can reference the network, contract address, method selector, method name, native value, gas limit, and decoded call arguments. |
| Web3 Transfer | Evaluated when a native-coin or ERC-20 transfer is sent through the Web3 raw-transaction API. Rules use the same withdrawal fields (amount, USD value, asset, destination, address book entry, internal wallet), so one condition reads the same on both triggers. ERC-20 transfers are also evaluated as Contract Call. |
| Web3 Message Signing | Evaluated when a wallet is asked to sign a message (`personal_sign`, `eth_sign`, `eth_signTypedData_v4`). Rules can detect token permits (EIP-2612, DAI, Permit2), their spender, tokens, amount, and deadline. Deny only: a signed message always needs approval. |

## How to Create a Policy

### Step 1: Open the Policies Page

In the left sidebar, open **Policies**, then click **Create Policy**.

### Step 2: Name the Policy and Choose an Approval Group

Give the policy a clear **Name** that describes what it protects (for example, "Large withdrawal guard"). Then select the [**Approval Group**](https://docs.fystack.io/product/approval-group.md) that will review changes to this policy.

Every policy is tied to an approval group. This means a policy cannot be silently created or edited, any change must be reviewed and approved, the same way a withdrawal is.

_Image: Create Policy Form_

> **Note:** The approval group is preselected with your workspace's default. You can
> change it to any group whose members should be responsible for reviewing this
> policy's rules.

### Step 3: Target Specific Wallets (Optional)

By default, a policy applies to **all wallets** in the workspace. To scope it to a subset, select one or more wallets in the **Wallets** field. The policy's rules will only be evaluated for actions on those wallets.

_Image: Wallet Selector_

### Step 4: Add Rules

Add one or more rules to the policy. For each rule, choose the **Trigger** (Withdrawal, Contract Call, Web3 Transfer, or Web3 Message Signing), build the **Condition**, and pick the **Effect** (Allow or Deny).

The condition builder shows the fields available for the selected trigger, so you can construct expressions without memorizing field names. To skip writing rules from scratch, start from one of the [Policy Templates](#policy-templates) below.

_Image: Add Rule Form_

Policies also programmable with JSON builder, which is useful for power users who want to copy/paste rules or use more complex logic than the UI supports.

_Image: Policy JSON Builder_

### Step 5: Submit for Approval

Click **Create**. Because every policy is approval-gated, the new policy is not active yet, it is submitted to its approval group as a **Pending Approval** request, along with all of its rules.

_Image: Policy Approval Request_

### Step 6: Approve the Policy

A member of the assigned approval group opens the pending request, reviews the proposed policy and its rules, and approves it. Only then does the policy become **Active** and start guarding transactions.

_Image: Approve Policy Modal_

> **Note:** The same approval flow applies to later policy and rule changes: editing a
> rule, adding a rule, or deleting the policy creates a new approval request.
> Nothing takes effect until the group signs off.

## Policy Templates

Start from a template instead of a blank policy. Each one below is a complete policy for a common wallet setup: what it protects, the rules in plain English, and JSON you can paste straight into the policy editor.

> **Note:** The numbers in each template are starting points, not recommendations for
> your volume. A good default is to set each Deny cap at about 2x to 3x your
> busiest normal day, then adjust after a week of real traffic.

### Using a Template

1. Go to **Policies**, click **Create Policy**, and switch the editor to the **JSON** tab.
2. Paste the template's **Policy JSON**.
3. Switch back to **Form**. Pick the **Approval Group** and the **Wallets** the policy should cover. The templates leave both empty on purpose, and an empty wallet list means every wallet in the workspace.
4. Open each rule and replace any placeholder in angle brackets, such as `<payout_api_key_id>`. Rules that refer to a member, API key, wallet, or address book entry show a picker in the builder, so you can select it by name instead of looking up its ID.
5. Adjust the limits to your own volume, then click **Create** and get the policy approved.

You can also send the same JSON to the [Policies API](https://docs.fystack.io/policies.md#create-a-policy).

### Choose a Template

| Template | Apply to | Auto-approves | Main limits |
| --- | --- | --- | --- |
| Treasury vault | Custody and treasury wallets | Nothing | Address book only, 24h cooling-off, 250,000 USD per day per wallet |
| Hot wallet payouts | Operational wallets used by your team | Small payouts to trusted addresses | 2,000 USD to unknown addresses, 50,000 USD per day, 60 per hour |
| API payout bot | Wallets an automated API key pays out from | Payouts up to 500 USD from one key | Stablecoins only, 2,000 USD per payout, 20,000 USD per day for the key |
| Deposit sweep | Customer deposit wallets | Sweeps to your treasury wallet | Funds can only move to the treasury wallet |
| DeFi operations | Wallets that use Web3 and dApps | Nothing | Approved contracts only, no unknown tokens, no risky signatures |
| Workspace backstop | All wallets | Nothing | 500,000 USD per day across the workspace, weekend freeze on large withdrawals |

> **Note:** Policies stack. Apply the **Workspace backstop** to all wallets, then add one
> template per wallet group. Because Deny always wins, the backstop also caps
> anything a narrower policy auto-approves.

### Treasury Vault

**Use it for**: wallets that hold reserves and move funds a few times a week. Nothing is auto-approved, so every transfer still goes to your approvers, and the rules block the transfers approvers should never be asked to sign.

**Wallets**: select your custody and treasury wallets.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block withdrawals to any external address that is not in the address book. | A typo or a swapped clipboard address can never be approved by mistake. |
| **Deny** | Block withdrawals to an address added to the address book less than 24 hours ago. | Someone who can edit the address book cannot add an address and drain funds to it on the same day. |
| **Deny** | Block a withdrawal that would push the wallet over 250,000 USD in 24 hours, counting pending withdrawals. | Limits the damage if approvers are compromised or tricked. |
| **Deny** | Apply the address book rule to native and ERC-20 transfers sent through the Web3 API. | Closes the path around the withdrawal rules. |

```json
{
  "name": "Treasury vault guard",
  "description": "Hard limits for custody and treasury wallets",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "withdrawal.create",
      "condition": "!withdrawal.destination_is_internal && !withdrawal.is_whitelisted",
      "effect": "DENY",
      "description": "Only send to address book entries or workspace wallets"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "withdrawal.is_whitelisted && withdrawal.address_age_sec < 86400",
      "effect": "DENY",
      "description": "New address book addresses wait 24 hours"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "wallet.projected_outflow_24h > 250000",
      "effect": "DENY",
      "description": "Cap each wallet at 250000 USD per 24 hours"
    },
    {
      "trigger_action": "web3.transfer",
      "condition": "!withdrawal.destination_is_internal && !withdrawal.is_whitelisted",
      "effect": "DENY",
      "description": "Apply the address book rule to Web3 transfers"
    }
  ]
}
```

**Tune it**

- Need a stricter cooling-off period? Change `86400` (24 hours) to `172800` (48 hours).
- To keep custody funds inside the workspace, so they can only move to your own hot wallets, add a Deny rule: `wallet.purpose == 'custody' && !withdrawal.destination_is_internal`.
- To cap a single asset in its own units, add `withdrawal.asset_symbol == 'BTC' && wallet.asset_projected_outflow_24h > 5`.

### Hot Wallet Payouts

**Use it for**: operational wallets your team pays out from every day. Small, routine payouts to known recipients skip approval, and everything else is either reviewed or blocked.

**Wallets**: select your hot or payment wallets.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block withdrawals over 2,000 USD to addresses that are not in the address book. | Larger amounts must go to a vetted address. |
| **Deny** | Block a withdrawal that would push the wallet over 50,000 USD in 24 hours. | Caps the worst case, including the auto-approved payouts. |
| **Deny** | Block withdrawals once the wallet has sent 60 in the last hour. | Catches a script or a compromised session that sends many small withdrawals. |
| **Allow** | Skip approval for withdrawals up to 1,000 USD to an address that has been in the address book for at least 24 hours. | Approvers stop spending time on routine payouts. |

**What happens to a withdrawal**

| Withdrawal | Result |
| --- | --- |
| 500 USD to an address book entry added last week | Auto-approved by the Allow rule |
| 500 USD to an address added an hour ago | Goes to your approvers, because the address is too new for the Allow rule |
| 1,500 USD to an unknown address | Goes to your approvers |
| 5,000 USD to an unknown address | Blocked |
| 800 USD to a trusted address when the wallet has already sent 49,500 USD today | Blocked, because Deny always wins over Allow |

```json
{
  "name": "Hot wallet payouts",
  "description": "Auto approve routine payouts and cap the worst case",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "withdrawal.create",
      "condition": "!withdrawal.destination_is_internal && !withdrawal.is_whitelisted && withdrawal.value_usd > 2000",
      "effect": "DENY",
      "description": "Unknown recipients are limited to 2000 USD"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "wallet.projected_outflow_24h > 50000",
      "effect": "DENY",
      "description": "Cap each wallet at 50000 USD per 24 hours"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "wallet.withdrawal_count_1h > 60",
      "effect": "DENY",
      "description": "Stop bursts of more than 60 withdrawals per hour"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "withdrawal.is_whitelisted && withdrawal.address_age_sec >= 86400 && withdrawal.value_usd <= 1000",
      "effect": "ALLOW",
      "description": "Auto approve small payouts to trusted addresses"
    }
  ]
}
```

**Tune it**

- Raise or lower the auto-approve limit (`1000`) first. Keep it well below the per-wallet daily cap.
- To auto-approve only some recipients, such as your exchange accounts, replace `withdrawal.is_whitelisted` in the Allow rule with **Destination · Address book entry** is any of the entries you trust.
- If your team works set hours, add the business-hours recipe from Rule Recipes below.

### API Payout Bot

**Use it for**: a backend service that sends customer payouts through an API key. Small payouts go out without waiting for approvers, medium ones are reviewed, and the key has a hard daily budget.

**Wallets**: select the wallets the payout service sends from.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block the payout key from sending anything except USDT and USDC. | A leaked key cannot move your other assets. |
| **Deny** | Block any single payout from the key over 2,000 USD. | Larger payouts must be created by a person. |
| **Deny** | Block the key once its payouts would exceed 20,000 USD in 24 hours, across all wallets. | A daily budget for the key, whichever wallet it uses. |
| **Allow** | Skip approval for payouts up to 500 USD from the key. | Customers get small payouts right away. Payouts from 500 to 2,000 USD go to your approvers. |

```json
{
  "name": "API payout bot",
  "description": "Budget and auto approval for the payout API key",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "withdrawal.create",
      "condition": "principal.api_key_id in ['<payout_api_key_id>'] && !(withdrawal.asset_symbol in ['USDT', 'USDC'])",
      "effect": "DENY",
      "description": "Payout key sends stablecoins only"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "principal.api_key_id in ['<payout_api_key_id>'] && withdrawal.value_usd > 2000",
      "effect": "DENY",
      "description": "Payout key cannot send more than 2000 USD at once"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "principal.api_key_id in ['<payout_api_key_id>'] && workspace.initiator_projected_outflow_24h > 20000",
      "effect": "DENY",
      "description": "Payout key is capped at 20000 USD per 24 hours"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "principal.api_key_id in ['<payout_api_key_id>'] && withdrawal.value_usd <= 500",
      "effect": "ALLOW",
      "description": "Auto approve payouts up to 500 USD"
    }
  ]
}
```

**Tune it**

- Replace `<payout_api_key_id>` in every rule, or pick the key with the **Initiator · API key** picker. Use the same key in all four rules.
- Customer payout addresses are usually not in your address book, so this template does not require it. The per-payout and daily caps carry the risk instead.
- Add more symbols to the list, such as `'DAI'`, if you pay out in other stablecoins.

### Deposit Sweep

**Use it for**: per-customer deposit wallets whose funds should only ever move to your treasury. The destination is fixed, so this is the safest flow to fully automate.

**Wallets**: select your customer deposit wallets, or leave the list empty to rely on the wallet purpose check.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block withdrawals from deposit wallets to anywhere except your treasury wallet. | Deposits can never leave the workspace directly. |
| **Allow** | Skip approval for transfers from deposit wallets to your treasury wallet. | Sweeps run without waiting for anyone. |

```json
{
  "name": "Deposit sweep",
  "description": "Deposit wallets sweep to treasury only",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "withdrawal.create",
      "condition": "wallet.purpose == 'user' && !(withdrawal.destination_wallet_id in ['<treasury_wallet_id>'])",
      "effect": "DENY",
      "description": "Deposit wallets can only send to the treasury"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "wallet.purpose == 'user' && withdrawal.destination_wallet_id in ['<treasury_wallet_id>']",
      "effect": "ALLOW",
      "description": "Auto approve sweeps to the treasury"
    }
  ]
}
```

**Tune it**

- Replace `<treasury_wallet_id>`, or pick it with the **Destination · Workspace wallet** picker.
- To allow more than one sweep target, add their IDs to both lists.

### DeFi Operations

**Use it for**: wallets that interact with dApps through the Web3 API. Every call still goes to your approvers, and the rules stop calls and signatures approvers often cannot judge from the raw data.

**Wallets**: select the wallets you connect to dApps.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block calls to any contract that is not on your list. | A phishing dApp cannot get a call to an unknown contract in front of approvers. |
| **Deny** | Block token approvals (`approve`, selector `0x095ea7b3`) on any contract except your token list. | Allowances are a common way to drain a wallet later. |
| **Deny** | Block transfers of tokens that are not in the Fystack asset catalog. | Unknown tokens have no USD value, so amount limits cannot apply to them. |
| **Deny** | Block signing a token permit with an unlimited amount. | A signed permit lets the spender move tokens later, with no further transaction from you. |
| **Deny** | Block signing a permit for any spender that is not on your list. | Permit phishing usually asks you to sign for an attacker spender. |
| **Deny** | Block `eth_sign` requests. | `eth_sign` can sign a hidden transaction, and modern dApps do not need it. |

```json
{
  "name": "DeFi operations guard",
  "description": "Approved contracts and safe signatures for dApp wallets",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "web3.contract_call",
      "condition": "!(resource.contract_address in ['<router_address>', '<vault_address>', '<token_address>'])",
      "effect": "DENY",
      "description": "Only call approved contracts"
    },
    {
      "trigger_action": "web3.contract_call",
      "condition": "resource.method_selector == '0x095ea7b3' && !(resource.contract_address in ['<token_address>'])",
      "effect": "DENY",
      "description": "Only approve allowances on known tokens"
    },
    {
      "trigger_action": "web3.transfer",
      "condition": "!withdrawal.asset_is_known",
      "effect": "DENY",
      "description": "Block transfers of unknown tokens"
    },
    {
      "trigger_action": "web3.sign_message",
      "condition": "resource.is_permit && resource.permit_amount_is_unlimited",
      "effect": "DENY",
      "description": "Block unlimited token permits"
    },
    {
      "trigger_action": "web3.sign_message",
      "condition": "resource.is_permit && !(resource.permit_spender in ['<trusted_spender_address>'])",
      "effect": "DENY",
      "description": "Block permits to untrusted spenders"
    },
    {
      "trigger_action": "web3.sign_message",
      "condition": "resource.method == 'eth_sign'",
      "effect": "DENY",
      "description": "Block raw eth_sign"
    }
  ]
}
```

**Tune it**

- Contract and token addresses must be lowercase.
- Add every contract your strategy uses, such as routers, vaults, and token contracts, before you enable the policy, or legitimate calls will be blocked.
- Message signing rules are Deny only. A signature that passes these rules still goes to your approvers.

### Workspace Backstop

**Use it for**: a safety net across every wallet. It catches a drain spread across many wallets, keys, or members, which a single wallet limit would miss.

**Wallets**: leave the list empty so the policy covers all wallets.

| Effect | Rule | Why |
| --- | --- | --- |
| **Deny** | Block a withdrawal that would push the whole workspace over 500,000 USD in 24 hours. | The absolute ceiling, whatever other policies allow. |
| **Deny** | Block withdrawals once the workspace has sent 300 in the last hour. | Catches automated abuse spread across wallets. |
| **Deny** | Block withdrawals over 25,000 USD on Saturday and Sunday (UTC). | Large transfers wait until your team is around to watch them. |

```json
{
  "name": "Workspace backstop",
  "description": "Workspace wide ceilings for all wallets",
  "approval_group_id": "",
  "wallet_ids": [],
  "rules": [
    {
      "trigger_action": "withdrawal.create",
      "condition": "workspace.projected_outflow_24h > 500000",
      "effect": "DENY",
      "description": "Cap the workspace at 500000 USD per 24 hours"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "workspace.withdrawal_count_1h > 300",
      "effect": "DENY",
      "description": "Stop bursts of more than 300 withdrawals per hour"
    },
    {
      "trigger_action": "withdrawal.create",
      "condition": "context.day_of_week in ['Saturday', 'Sunday'] && withdrawal.value_usd > 25000",
      "effect": "DENY",
      "description": "No withdrawals over 25000 USD on weekends"
    }
  ]
}
```

**Tune it**

- Days and hours are in UTC.
- Set the workspace cap above the sum of your normal busiest days across all wallets, so the backstop only fires on real anomalies.

### Rule Recipes

Single rules you can add to any policy. Paste the condition into the **Expression** tab of a rule, or build it in **Fields**.

| Goal | Trigger | Effect | Condition |
| --- | --- | --- | --- |
| Block an address list, such as sanctioned or scam addresses | Withdrawal | Deny | `` `withdrawal.address_book_id in ['<blocklist_entry_id>']` ``. Keep the addresses in one address book entry, so updating the entry updates the rule. |
| Only owners and admins can send over 50,000 USD | Withdrawal | Deny | `` `!(principal.role in ['owner', 'admin']) && withdrawal.value_usd > 50000` `` |
| Only allow large withdrawals during business hours (09:00 to 18:00 UTC) | Withdrawal | Deny | `` `withdrawal.value_usd > 25000 && (context.hour < 9 \|\| context.hour >= 18)` ``. Uses brackets, so write it in the Expression tab. |
| Cap a wallet at 5 BTC per day | Withdrawal | Deny | `` `withdrawal.asset_symbol == 'BTC' && wallet.asset_projected_outflow_24h > 5` `` |
| Cap one network at 100,000 USD per day | Withdrawal | Deny | `` `withdrawal.network_code == '<network_code>' && workspace.network_projected_outflow_24h > 100000` `` |
| Auto-approve transfers between your own wallets | Withdrawal | Allow | `` `withdrawal.destination_is_internal && withdrawal.value_usd <= 10000` `` |
| Auto-approve top-ups to trusted exchange accounts | Withdrawal | Allow | `` `withdrawal.address_book_id in ['<exchange_entry_id>'] && withdrawal.address_age_sec >= 172800 && withdrawal.value_usd <= 25000` `` |
| Stop work piling up in the approval queue | Withdrawal | Deny | `` `wallet.pending_outflow > 100000` ``. Blocks new requests while 100,000 USD is already waiting. |
| Block unusually expensive contract calls | Contract Call | Deny | `` `resource.gas_limit > 1000000` `` |

### Rolling Out a Policy

Allow rules change who has to look at a transaction, so introduce them last.

1. **Week 1, Deny rules only**: create the policy with only its Deny rules. Nothing is auto-approved yet, and the rules only block what should never happen.
2. **Review the approval queue**: look for withdrawals your approvers approve unchanged every time, such as the same recipients, assets, and amount range.
3. **Add a narrow Allow rule**: write an Allow rule for exactly that traffic, with a lower limit than you expect to need.
4. **Raise limits slowly**: widen the Allow rule only after it has run cleanly for a while. Each change goes through approval and appears in the policy's [Change History](#change-history).

> **Note:** When you add an Allow rule, check that a Deny cap covers the same wallets,
> for example a per-wallet or per-key projected outflow limit. Deny always
> wins, so the cap still applies to auto-approved withdrawals.

## Writing Rule Conditions

A condition is an expression that evaluates to true or false. When it is true, the rule's effect applies. Combine fields with `and`, `or`, and `not`, and compare values with operators like `>`, `>=`, `==`, and `!=`.

_Image: Condition Builder_

> **Note:** You can validate a condition before saving it. The builder checks the
> expression against the trigger's available fields and flags any errors, so you
> never submit a rule that cannot run.

### Condition Fields

The fields available in the builder depend on the rule's trigger. Use the labels below when deciding which field belongs in a rule.

#### Available on Every Trigger

| Field | Use it for |
| --- | --- |
| Initiator type | Whether the action came from a web session (`user`) or an API key (`api_key`). |
| Member | Rules for specific workspace members. Pick them from the member list. |
| API key | Rules for specific API keys, for example an automated payout key. Web sessions have no API key, so combine a `is none of` check with Initiator type. |
| User role | Rules based on workspace role: owner, admin, signer, proposer, or viewer. |
| Hour | Time-of-day controls using UTC hour from 0 to 23. |
| Day of week | Business-hour, weekend, or weekday-only rules. |
| Source wallet | Rules for specific wallets inside one policy. |
| Source wallet purpose | Rules by wallet purpose: general, custody, payment, user, one-time use, gas tank, or deployment. |
| Source wallet type | Different controls for MPC and standard wallets. |
| User email (legacy) | Still evaluated for existing rules. New rules use Member, because an email can change. |

#### Withdrawal Fields

| Field | Use it for |
| --- | --- |
| Amount | Asset-denominated limits, such as more than 10 ETH or 1 BTC. |
| Value USD | Dollar-denominated limits across assets. |
| Asset symbol | Asset-specific rules for symbols such as USDT, ETH, BTC, or SOL. |
| Native asset | Different controls for native network assets versus tokens. |
| Destination address | Blocking or allowing specific recipient addresses. |
| Network code | Network-specific limits, such as stricter rules on one chain. |
| In address book | Controls for recipients that are or are not in any address book entry. |
| Address book entry | Controls for specific address book entries. One entry covers all of its addresses, so an entry works as a named list. |
| Address age | Cooling-off rules: seconds since the destination address was added to its address book entry. |
| Internal wallet | Controls for transfers between wallets of the same workspace. |
| Workspace wallet | Controls for transfers to specific wallets of the same workspace. |

> **Note:** Fields that refer to a member, an API key, an address book entry, or a
> wallet use a picker in the condition builder. Fystack stores the ID, shows the
> name everywhere the rule appears (including approval requests), and checks
> when you save that each ID belongs to your workspace. If an entity is later
> deleted or revoked, the rule shows it as an unknown reference and the rule
> can only match less, never more.

#### Withdrawal Activity Fields

| Field | Use it for |
| --- | --- |
| Wallet outflow | Per-wallet USD outflow over the last 1, 6, or 24 hours. |
| Wallet withdrawal count | Burst controls for many withdrawals from one wallet in the last hour. |
| Workspace outflow | Workspace-wide USD outflow over the last 1, 6, or 24 hours. |
| Workspace withdrawal count | Workspace-wide withdrawal volume in the last hour. |
| Network outflow | Workspace USD outflow on the selected network over the last 1, 6, or 24 hours. |
| Network withdrawal count | Workspace withdrawal volume on the selected network in the last hour. |

#### Pending and Projected Activity Fields

| Field | Use it for |
| --- | --- |
| Pending outflow | USD value of withdrawals that are not executed yet (waiting for approval, queued, or funding gas), whatever their age. Available per wallet, workspace, and network. |
| Projected 24h outflow | Realized outflow in the last 24 hours, plus pending outflow, plus the withdrawal being checked. Use it for limits: `projected 24h outflow over $100,000` means this withdrawal would exceed the limit. Available per wallet, workspace, and network. |
| Projected 24h asset outflow | The same projection for the source wallet, in units of the withdrawn asset, for example at most 5 BTC per day. |
| Initiator projected 24h outflow | The same projection for withdrawals created by the same member or API key, across the workspace. |

> **Note:** Outflow and count fields are calculated from realized withdrawals: they count
> funds after a withdrawal has been executed or confirmed. Pending and projected
> fields also count withdrawals that are not executed yet. When an active rule
> uses a pending or projected field, Fystack checks the workspace's withdrawals
> one at a time, so two requests sent at the same moment cannot both pass a
> limit. A request that waits longer than 5 seconds for its turn is rejected
> with HTTP 429 and can be retried.

#### Contract Call Fields

| Field | Use it for |
| --- | --- |
| Network code | Chain-specific contract call rules. |
| Contract address | Blocking unknown contracts or allowing known contracts. |
| Method selector | Low-level function checks, such as ERC-20 transfer selector `0xa9059cbb`. |
| Method name | Human-readable method checks when metadata is available. |
| Value wei | Native value sent with a contract call. |
| Gas limit | Blocking unusually expensive or risky calls. |
| Decoded arguments | Argument-level rules when Fystack can decode the call. ERC-20 transfers expose recipient and token value. |

> **Note:** Decoded arguments are only available when the call can be decoded. In the
> JSON builder, guard decoded argument checks before reading a value.

#### Web3 Transfer Fields

Web3 Transfer rules use the [Withdrawal Fields](#withdrawal-fields) plus the
fields below. Activity fields are not available, because these transfers are
not withdrawals.

| Field | Use it for |
| --- | --- |
| Asset in catalog | False when the token contract is not in the asset catalog. Amount, USD value, and symbol are then empty, so deny unknown tokens explicitly if you need to. |
| Token contract | Rules for a specific token contract. Empty for native coins. |

#### Message Signing Fields

| Field | Use it for |
| --- | --- |
| Signing method | `personal_sign`, `eth_sign`, or `eth_signTypedData_v4`. |
| Typed data type, domain name, verifying contract | Rules for specific typed-data messages, such as a known dApp's orders. |
| Is a token permit | True for EIP-2612, DAI, and Permit2 messages, which let the spender move tokens later without another signature. |
| Permit spender | Deny permits to spenders you do not trust. |
| Permit tokens | The token contracts the permit covers. Use it in the expression editor, for example `'0x…' in resource.permit_tokens`. |
| Permit unlimited amount | True for max-value allowances (2^159 or more, or a DAI permit with allowed set). |
| Permit deadline | Unix time the permit or its signature expires. 0 when the message has none. |

## Policy Statuses

| Status | Meaning |
| --- | --- |
| Pending | The policy, or a change to it, is awaiting sign-off from its approval group. It is not yet enforced. |
| Active | The policy is approved and live. Its rules are evaluated against every guarded action on the targeted wallets. |
| Inactive | The policy is disabled and not evaluated. |
| Rejected | A pending change was rejected by an approver and did not take effect. |

## Change History

Every change to a policy is recorded. The policy's **History** shows each revision with who proposed it, what changed (field by field), and whether it was approved or rejected. This gives you a complete audit trail of how your guardrails evolved over time.

_Image: Policy Change History_

## Best Practices

- Start with **Deny** rules for your highest-risk scenarios (large transfers, unknown destinations) before adding convenience **Allow** rules
- Remember that **Deny always wins**, use this to set hard limits that no Allow rule can override
- Scope policies to specific wallets when different wallets have different risk profiles, rather than applying one broad policy everywhere
- Use **Allow** rules sparingly, they bypass your approval queue, so reserve them for low-value, clearly trusted patterns
- When an **Allow** rule trusts address book entries, keep the address age condition the builder adds. Anyone who can edit the address book can add an address to a trusted entry, and the age check makes a new address wait before it is auto-approved
- Validate every condition before submitting, and review the change in the approval step to confirm it behaves as intended
- Treat policy changes with the same care as withdrawals, the approval gate exists so no single person can weaken your controls unnoticed
