Skip to main content
A policy governs which transactions a wallet can authorize. Every wallet must have at least one policy attached before it can sign outbound transactions — Dakota’s policy engine default-denies any transaction on a wallet with zero attached policies ("transaction denied: No policies found for wallet", HTTP 403). Inbound deposits are independent of policies: an on-chain address can always receive crypto regardless of the wallet’s governance. Policies are versioned, mutable resources. You attach them to wallets, and a wallet can have multiple policies layered on top of one another. The policy engine evaluates all attached policies for every transaction and applies the result.

Anatomy of a Policy

A policy is three things:
  1. A signer group that governs the policy itself. Future mutations of the policy — adding rules, deleting it, updating limits — must be signed by members of this group via the endorsed-request flow. It can be the same group attached to the wallet, or a separate admin group for stricter compartmentalization. A policy’s approval_threshold and amount_threshold rules count signatures only from its own signer group, and only while that group is also attached to the wallet. If a separate admin group is not attached to the wallet, those rules see no signatures, so an allow threshold rule is never met and denies the transactions it covers.
  2. One or more rules. Each rule has a rule_type, an action (allow or deny), and a definition whose shape depends on the rule type.
  3. Metadata. A human-readable name and optional description.

Rule Types

There are three rule types. Each evaluates a different aspect of the transaction.

approval_threshold

Requires a minimum number of valid signatures from the policy’s signer group.
  • threshold — minimum number of signers required.
  • The rule always applies to every transaction (it’s a count check, not a conditional).
  • If the threshold is met → emit the rule’s action (allow or deny). If not met → emit the inverted action.
The minimum permissive policy — “any single member can authorize anything” — is approval_threshold with threshold: 1 and action: allow.

amount_threshold

Requires a minimum number of approvals once a transaction reaches a value you name. Useful for tiered approval: small transfers single-sig, large transfers multi-sig. With threshold: 0 on a deny rule, it is a hard cap instead.
  • asset — required; there is no way to omit it. id is the asset symbol — USDC, USDT, or RD — and is stored upper case. A missing asset, or an id that is empty or any, is rejected with 400. You may also pass name, a display label that plays no part in matching and can be left out entirely.
  • min_amount — integer, 0 or greater, in the asset’s smallest unit. USDC has 6 decimals, so the 10000000000 above is 10,000 USDC.
  • threshold: number of approvals required, 0 or greater. 0 is valid only on deny rules and makes the rule unconditional (see Hard cap). An allow rule with threshold: 0 returns 400.
  • The rule applies only when the transaction moves asset and its amount is at or above min_amount. Below min_amount the rule does not apply (does not contribute allow or deny).
  • The rule matches on the asset symbol, so it governs that asset on every network unless the stored asset names one. Amounts are compared in the asset’s smallest unit.
  • When the rule applies: if the transaction carries at least threshold approvals → emit the rule’s action. If it carries fewer → emit the inverted action. So the example above allows a transfer of 10,000 USDC or more only when two signers approve, and denies it otherwise.

Hard cap

A deny rule with threshold: 0 is a hard cap:
  • The rule denies every transaction in its asset at or above min_amount, regardless of approvals. Every approval count, zero included, meets a threshold of 0, so the rule always emits its own action. The example denies any transfer of 50,000 USDC or more.
  • A transaction below min_amount is not affected, because the rule does not apply to it.
  • A deny from any applied rule wins over an allow from another rule or policy (see Evaluation Order). More approvals or another policy cannot lift the cap.
  • 0 is valid only on deny rules. An allow rule with threshold: 0 would allow every transaction at or above min_amount with no approval, so the API refuses it.
  • A cap binds only where its policy applies, which is on the wallets the policy is attached to. On a card-enabled wallet, the customer policy applies only to transactions that its own signer group endorses, so a cap in that policy does not stop card settlement.

address_list

Restricts which on-chain destinations a wallet can send to. Allow-list (only these) or deny-list (block these).
  • The rule applies only when the destination address is in the addresses list.
  • For an allow-list, set action: allow and pair with a default-deny in another rule (or rely on the policy engine’s default-deny on no-applicable-policy).
  • For a deny-list, set action: deny and the listed addresses are blocked outright.

Evaluation Order

For a given transaction, the policy engine:
  1. Loads every policy attached to the wallet. If zero policies, return deny immediately ("No policies found for wallet").
  2. For each policy, evaluates each rule in turn. A rule produces one of: applied + allow, applied + deny, or not-applicable.
  3. Aggregates per policy: if any rule denies → policy = deny. Else if any rule allows → policy = allow. Else (no rule applied) → policy = not-applicable.
  4. Aggregates across policies: if any policy denies → transaction = deny. Else if any policy allows → transaction = allow. Else (no applicable policy) → transaction = deny (default).
Translation: deny wins, allow loses, silence is deny. Layering policies is safe — adding more policies can only block transactions, never relax existing controls.

Lifecycle

Policy creation itself is not endorsed — it’s a regular API request authenticated only by your API key. Once the policy exists, every subsequent mutation goes through the endorsed-request flow: canonicalize the intent per RFC 8785 JCS, sign with ECDSA P-256, encode as DER + base64.

Practical Examples

”Default permissive” — single signature, no restrictions

For sandbox testing or low-risk accounts:

“Tiered approval by amount” — single-sig under $10k, dual-sig above

Below 10,000 USDC the amount_threshold rule does not apply, so a single signature is enough. At or above it the rule applies and emits allow only once two signers have approved — with one signature it emits the inverted action, and deny wins.

”Sanctions deny-list” — block specific addresses outright

Stack this alongside other policies — its deny will override any allow from sibling policies.