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

# Policies

> Restrict transfers with Privy policies.

Apps can use Privy policies to restrict which transfer actions a wallet or signer can take. For
transfers, Privy evaluates policies against the original request body sent to the
[`transfer`](/api-reference/wallets/transfer) endpoint before it prepares the underlying
transactions.

That means transfer rules should match request-body fields like `source.asset`, `source.amount`,
`source.chain`, and `destination.address`.

## Supported methods

Transfer policies support a single rule method:

* `transfer`

<Warning title="Method matching is exact">
  If a wallet policy only allows `eth_sendTransaction`, transfer requests will still be denied.
  Wallets that call the transfer endpoint need an explicit `transfer` rule.
</Warning>

## Supported conditions

Transfer rules support `action_request_body` conditions for the fields below:

<table>
  <colgroup>
    <col style={{width: '24%'}} />

    <col style={{width: '22%'}} />

    <col style={{width: '22%'}} />

    <col style={{width: '32%'}} />
  </colgroup>

  <thead>
    <tr>
      <th style={{whiteSpace: 'nowrap'}}>Field source</th>
      <th style={{whiteSpace: 'nowrap'}}>Field</th>
      <th>Supported operators</th>
      <th>Notes</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>source.asset</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>
        Matches a named asset such as <code>"usdc"</code> or <code>"eth"</code>.
      </td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>source.asset\_address</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>Matches a custom token contract address.</td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>source.amount</code>
      </td>

      <td>
        <code>eq</code>, <code>gt</code>, <code>gte</code>, <code>lt</code>, <code>lte</code>
      </td>

      <td>
        Value must be a positive decimal string, such as <code>"10.5"</code>.
      </td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>source.chain</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>
        Matches the source chain, such as <code>"base"</code> or <code>"solana"</code>.
      </td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>destination.address</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>Matches the recipient wallet address.</td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>destination.asset</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>Matches the destination asset for cross-asset transfers.</td>
    </tr>

    <tr>
      <td style={{whiteSpace: 'nowrap'}}>
        <code>action\_request\_body</code>
      </td>

      <td style={{whiteSpace: 'nowrap'}}>
        <code>destination.chain</code>
      </td>

      <td>
        <code>eq</code>, <code>in</code>, <code>in\_condition\_set</code>
      </td>

      <td>Matches the destination chain for cross-chain transfers.</td>
    </tr>
  </tbody>
</table>

Transfer rules also support shared `system` conditions, such as `current_unix_timestamp`, for
time-based controls.

<Info>
  Transfer policies support both `chain_type: "ethereum"` and `chain_type: "solana"`. For transfer
  methods, the policy engine only accepts `action_request_body` and `system` conditions.
</Info>

## Choose `source.asset` or `source.asset_address`

Use `source.asset` if your app sends named assets like `"usdc"` or `"eth"`. Use
`source.asset_address` if your app sends custom token contract addresses.

<Warning>
  A single rule cannot condition on both `source.asset` and `source.asset_address`. A transfer
  request includes one or the other, never both.
</Warning>

This also affects runtime matching:

* A rule using `source.asset` will not match a request that sends `source.asset_address`.
* A rule using `source.asset_address` will not match a request that sends `source.asset`.

Keep your policy format aligned with the request format your application actually sends.

## Example

The example below allows:

* transfers of USDC up to `1000` units to an approved destination address
* transfers on the Base chain only

After creating the policy, apply it to the wallet with `policy_ids`. For signer-specific transfer
permissions, attach the policy as an override policy on a signer instead.

<Tabs>
  <Tab title="Node SDK">
    ```typescript theme={"system"}
    const policy = await privy.policies().create({
      name: 'Approved transfer policy',
      version: '1.0',
      chain_type: 'ethereum',
      rules: [
        {
          name: 'Allow USDC transfers up to 1000 to approved address on Base',
          method: 'transfer',
          action: 'ALLOW',
          conditions: [
            {
              field_source: 'action_request_body',
              field: 'source.asset',
              operator: 'eq',
              value: 'usdc',
            },
            {
              field_source: 'action_request_body',
              field: 'source.amount',
              operator: 'lte',
              value: '1000.0',
            },
            {
              field_source: 'action_request_body',
              field: 'source.chain',
              operator: 'eq',
              value: 'base',
            },
            {
              field_source: 'action_request_body',
              field: 'destination.address',
              operator: 'eq',
              value: '<approved-address>',
            },
          ],
        },
      ],
    });

    await privy.wallets().update('<wallet-id>', {
      policy_ids: [policy.id],
    });
    ```

    If the wallet has an `owner_id`, the wallet update must be authorized by that owner.
  </Tab>

  <Tab title="REST API">
    Create the policy:

    ```bash theme={"system"}
    curl -X POST https://api.privy.io/v1/policies \
      -H "privy-app-id: <your-app-id>" \
      -H "Authorization: Basic <credentials>" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Approved transfer policy",
        "version": "1.0",
        "chain_type": "ethereum",
        "rules": [
          {
            "name": "Allow USDC transfers up to 1000 to approved address on Base",
            "method": "transfer",
            "action": "ALLOW",
            "conditions": [
              {
                "field_source": "action_request_body",
                "field": "source.asset",
                "operator": "eq",
                "value": "usdc"
              },
              {
                "field_source": "action_request_body",
                "field": "source.amount",
                "operator": "lte",
                "value": "1000.0"
              },
              {
                "field_source": "action_request_body",
                "field": "source.chain",
                "operator": "eq",
                "value": "base"
              },
              {
                "field_source": "action_request_body",
                "field": "destination.address",
                "operator": "eq",
                "value": "<approved-address>"
              }
            ]
          }
        ]
      }'
    ```

    Then apply the returned policy ID to a wallet:

    ```bash theme={"system"}
    curl -X PATCH https://api.privy.io/v1/wallets/<wallet-id> \
      -H "privy-app-id: <your-app-id>" \
      -H "Authorization: Basic <credentials>" \
      -H "Content-Type: application/json" \
      -d '{
        "policy_ids": ["<policy-id>"]
      }'
    ```
  </Tab>
</Tabs>

## Common patterns

* Restrict transfers to an allowlist of destination addresses with `destination.address: in` or `destination.address: in_condition_set`.
* Limit which assets can be transferred with `source.asset` or `source.asset_address`.
* Enforce maximum transfer size with `source.amount`.
* Lock transfers to a specific chain with `source.chain`.
* Add time-based controls with `system.current_unix_timestamp`.
* Combine chain and destination controls for cross-chain transfer restrictions.

## Next steps

<CardGroup cols={2}>
  <Card title="Create a policy" icon="shield" href="/controls/policies/create-a-policy" arrow>
    Learn more about creating and managing policy objects.
  </Card>

  <Card title="Conditional policies per signer" icon="key" href="/recipes/wallets/conditional-signer-policies" arrow>
    Apply different transfer permissions to different signers on the same wallet.
  </Card>
</CardGroup>
