---
title: Wallet policies
description: Wallet controls, operation permissions, and policy audit visibility.
---

Wallet policies constrain how a custody wallet can move funds and execute supported operations. SDP starts from default allow, then applies the active wallet control profile and any policy fields you configure. Policies are managed via `GET` and `PUT` on `/v1/payments/wallets/{walletId}/policies` and enforced before wallet operations execute.

Use policies for treasury wallets, automated payout wallets, or any custody wallet where unbounded outbound flow would be a liability.

## Reading a wallet's policies

<Tabs items={["curl", "TypeScript"]}>
<Tab value="curl">
```bash
curl https://api.solana.com/v1/payments/wallets/wal_abc123/policies \
  -H "Authorization: Bearer sk_test_..."
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
  "https://api.solana.com/v1/payments/wallets/wal_abc123/policies",
  { headers: { Authorization: "Bearer sk_test_..." } }
);
const { data } = await response.json();
// data.policy.destinationAllowlist: string[]
// data.policy.maxTransferAmount?: string
// data.policy.maxDailyAmount?: string
// data.policy.controlProfile?.providerMappingStatus
// data.policy.audit?.recentEvaluations
```
</Tab>
</Tabs>

The payload is wrapped in the standard `data` / `meta` envelope, with the policy under a `policy` key:

```json
{
  "data": {
    "policy": {
      "walletId": "wal_abc123",
      "destinationAllowlist": ["7xKXz...9fGh", "5aBCd...2eFg"],
      "maxTransferAmount": "100.00",
      "maxDailyAmount": "1000.00",
      "defaultAction": "allow",
      "controlProfile": {
        "id": "wcp_abc123",
        "status": "active",
        "activeRevisionId": "wcpr_abc123",
        "revisionId": "wcpr_abc123",
        "revisionNumber": 3,
        "defaultAction": "allow",
        "rules": [
          {
            "id": "approval-required",
            "kind": "approval",
            "families": ["payment", "ramp"],
            "action": "approval_required",
            "name": "Approval checks"
          }
        ],
        "providerMappingStatus": "not_applicable",
        "createdAt": "2026-05-14T08:00:00Z",
        "updatedAt": "2026-05-14T10:15:00Z",
        "activatedAt": "2026-05-14T10:15:00Z"
      },
      "audit": {
        "recentEvaluations": [
          {
            "walletOperationId": "wop_abc123",
            "policyEvaluationId": "peval_abc123",
            "operationFamily": "payment",
            "operationType": "payment_transfer_execute",
            "asset": "USDC",
            "amount": "100.00",
            "destination": "7xKXz...9fGh",
            "status": "pending_approval",
            "decision": "approval_required",
            "reasonCode": "wallet_policy_match",
            "reason": "Payment matched approval policy.",
            "requiresApproval": true,
            "approvalRequestId": "appr_abc123",
            "operationCreatedAt": "2026-05-14T10:20:00Z",
            "operationUpdatedAt": "2026-05-14T10:20:00Z",
            "evaluatedAt": "2026-05-14T10:20:00Z"
          }
        ]
      },
      "createdAt": "2026-05-14T08:00:00Z",
      "updatedAt": "2026-05-14T10:15:00Z"
    }
  },
  "meta": { "requestId": "req_...", "timestamp": "2026-05-18T00:00:00.000Z" }
}
```

Amount fields are **UI-unit decimal strings** (`"100.00"` means 100 tokens, regardless of the mint's decimals — matching the transfer-request convention). The configured value is a **single threshold**, but enforcement is **per token**: `maxDailyAmount` sums each token's outbound transfers separately within the day window, so a wallet with `maxDailyAmount: "1000"` can send up to 1000 USDC *and* 1000 SOL in the same UTC day — there is no aggregate cap across mints. If you need different per-token thresholds (e.g., 10,000 USDC daily but 50 SOL daily), partition into separate wallets.

## Default allow and active controls

Wallet policy evaluation starts from default allow. If no wallet control profile exists, supported wallet operations continue to run unless another product-specific check rejects them. When you activate a control profile, SDP evaluates its rules first and returns the resulting `decision` as one of `allow`, `deny`, `approval_required`, `provider_approval_required`, `review`, or `not_evaluated`.

The `controlProfile` object shows the active immutable revision. Treat `revisionId` and `revisionNumber` as audit references: a policy evaluation records which revision was active when the wallet operation was checked.

### Dashboard actions

The dashboard authoring flow exposes three actions: **Allow**, **Deny**, and **Require approval**. Choose **Require approval** when an operator must decide whether an operation can continue; it holds the operation and creates an approval request.

`review` remains a possible API evaluation result for legacy or safety paths, such as an unrecognized policy rule. It is visible in audit records, but it is not a dashboard authoring option or a separate human-review workflow. Do not use it to model operator approval; use `approval_required` instead.

## Operation permissions

Wallet policies can match operations at two levels:

- **Operation family** rules apply to every evaluated operation in a broad product area.
- **Operation type** rules match one exact operation identifier and take precedence when you need a narrower decision.

These are wallet-policy selectors, not API-key permissions. API-key permissions such as `payments:write` and `tokens:write` determine whether a caller may invoke an endpoint; operation permissions determine what the selected wallet may do after the request is authorized. See [Manage API keys](/docs/guides/manage-api-keys) for caller permissions.

### Operation families

| Family | Description | Current evaluated operations |
| --- | --- | --- |
| `payment` | Outbound single and batch payments. | Single and batch transfer execution |
| `ramp` | Fiat-to-crypto and crypto-to-fiat ramp activity. | On-ramp and off-ramp quote creation |
| `issuance` | Token lifecycle actions signed by the wallet. | Mint execution and authority updates |
| `raw_sign` | Low-level signing and signer verification. | Custody signer checks |
| `transfer` | Generic direct-transfer family reserved for operations outside the payment workflow. | No current public operation type |
| `program` | General Solana program interactions. | No current public operation type |
| `provider_admin` | Custody-provider administration. | No current public operation type |

Family values with no current public operation type are valid policy values, but they do not match a public SDP flow until an operation is emitted in that family.

### Exact operation types

| Operation type | Family | What it controls |
| --- | --- | --- |
| `payment_transfer_execute` | `payment` | Executes one outbound payment transfer from the wallet. |
| `payment_transfer_batch_execute` | `payment` | Executes an outbound batch and evaluates the aggregate amount before submission. |
| `ramp_onramp_quote` | `ramp` | Creates an on-ramp quote that delivers crypto to the destination wallet. |
| `ramp_offramp_quote` | `ramp` | Creates an off-ramp quote that draws crypto from the source wallet. |
| `issuance_mint_execute` | `issuance` | Executes a token mint using the configured signing wallet. |
| `issuance_update_authority_execute` | `issuance` | Changes a token authority using the current authority wallet. |
| `custody_signer_check` | `raw_sign` | Runs the custody signer-check transaction for the wallet. |

Operation-type matching is exact and case-sensitive. The API accepts custom strings up to 120 characters so future or privately integrated operations can be represented, but a rule only affects an operation when SDP emits the same identifier. For current public flows, use one of the identifiers above.

## Policy evaluation and audit records

Each supported wallet operation records a wallet operation row before policy evaluation and a policy evaluation row after SDP decides what should happen. `audit.recentEvaluations` exposes the most recent decisions for the wallet so support and customer operators can review:

- what operation was evaluated (`walletOperationId`, `operationFamily`, `operationType`)
- what decision SDP made (`decision`, `reasonCode`, `reason`, `requiresApproval`)
- whether an approval request was created (`approvalRequestId`)
- when the operation was created, updated, and evaluated

Use these records to explain allow, deny, approval-required, and review outcomes. They are an operational audit summary, not the full raw provider payload.

## Provider mapping status

`providerMappingStatus` describes whether an SDP policy revision has also been mapped into a custody or ramp provider's native policy system. Current payment wallet controls are SDP-enforced first. A value of `not_applicable` means there is no provider-native mapping for that revision. `pending`, `partial`, or `failed` mean SDP still evaluates the operation, but you should not assume the same rule has been fully synced to the provider.

## Destination allowlist

The allowlist is a list of on-chain destination addresses (32-44 character base-58 Solana pubkeys). When set, `POST /v1/payments/transfers` rejects any destination not in the list.

- Maximum entries: **500 addresses per wallet**.
- Each entry is a single on-chain address — no patterns, ranges, or address books.
- Empty allowlist (`[]`) means no destination restriction. To enable enforcement, populate at least one entry.

The allowlist exists to bound risk on automated wallets (a hot wallet that should only ever pay a known set of counterparties) and to enforce treasury controls (only the corporate cold-storage address can drain the operating wallet).

## Transfer limits

Two limits, both optional:

- **`maxTransferAmount`** — per-transfer cap. Any single `POST /v1/payments/transfers` exceeding this amount is rejected. Compared against the request's `amount` directly, so it is naturally per-token.
- **`maxDailyAmount`** — UTC-calendar-day cap, **enforced per token**. The day window resets at 00:00 UTC; the projected total is the sum of that wallet's outbound transfers for the **same token** in the current day (`pending`/`processing`/`confirmed`/`finalized`) plus the new request's `amount`. Different tokens are summed independently — see the per-token note under [Reading a wallet's policies](#reading-a-wallets-policies).

Set both for defense in depth: a per-transaction cap that catches obvious mistakes, plus a daily cap that bounds blast radius if many small transfers are submitted in a coordinated attack.

## Updating policies

`PUT /v1/payments/wallets/{walletId}/policies` has **full-replace** semantics — the request body becomes the new policy state in its entirety. To remove the allowlist, send `destinationAllowlist: []`. To drop a transfer limit, omit the field on the next PUT.

<Tabs items={["curl", "TypeScript"]}>
<Tab value="curl">
```bash
curl -X PUT https://api.solana.com/v1/payments/wallets/wal_abc123/policies \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "destinationAllowlist": [
      "7xKXz...9fGh",
      "5aBCd...2eFg"
    ],
    "maxTransferAmount": "100.00",
    "maxDailyAmount": "1000.00"
  }'
```
</Tab>
<Tab value="TypeScript">
```typescript
await fetch(
  "https://api.solana.com/v1/payments/wallets/wal_abc123/policies",
  {
    method: "PUT",
    headers: {
      "Authorization": "Bearer sk_test_...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      destinationAllowlist: ["7xKXz...9fGh", "5aBCd...2eFg"],
      maxTransferAmount: "100.00",
      maxDailyAmount: "1000.00",
    }),
  }
);
```
</Tab>
</Tabs>

Because PUT is full-replace, treat policy edits like ordinary form submissions: read the current state, present it for editing, and PUT the complete updated object. Don't construct partial patches.

## Interaction with compliance

Wallet policies enforce **structural** constraints (where funds can go, how much, how fast). They do not replace compliance screening. If your organization has compliance screening enabled, screening happens on the destination address as a separate check, in addition to the allowlist match. A transfer can be rejected by either layer; both must pass to reach the network.

See the [Compliance API reference](/docs/reference/api/compliance) for address-screening flows that complement policies.

## Related

- [Payouts and disbursements](/docs/payments/send-payouts) — running an outbound batch from a policy-constrained wallet.
- [Basic payment](/docs/payments/send-basic-payment) — the underlying transfer endpoint subject to policy enforcement.
- [Wallet balances](/docs/wallet-operations/balances) — current balance, useful to size transfer limits.
