A user initiates a transaction from a Dfns wallet. The policy engine intercepts the request and puts it inDocumentation Index
Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
Use this file to discover all available pages before exploring further.
Pending. A service account fetches the pending approval, decodes the ABI-encoded call data, evaluates the function name, recipient and amount against your business rules, and posts a decision. Every decision is cryptographically signed, preserving full non-repudiation regardless of who approves.
Static policies decide on amounts and addresses. Smart-contract calls need more: the meaning of the transaction is encoded in ABI-packed call data, not in value or to. Decoding the call lets the checker enforce per-function rules in plain TypeScript.
Get the code
dfns/dfns-solutions: programmable-policy
When to use this
- Custom contract rules: only allow
mintto whitelisted recipients, only allowapprovefor known spenders, cap atransferamount per call - Compliance checks: screen recipients against sanctions lists or internal rules before they execute
- Speed: remove human bottlenecks for routine operations while keeping oversight on exceptions
How it works
The service account and one or more human approvers are listed in the same approval group with a quorum of 1. Whoever approves first satisfies the requirement: the service account handles the routine cases, humans handle the exceptions.Reference implementation
The recipes repo deploys a sample ERC-20 stablecoin, creates the policy, and runs a service-account checker against pendingmint calls.
| Component | Technology |
|---|---|
| Contract | Solidity 0.8.28 ERC-20 (OpenZeppelin, Hardhat v3) |
| Signing | Dfns KMS via @dfns/sdk |
| Scripts | TypeScript (tsx, viem) |
| Network | Ethereum Sepolia |
What you’ll need
- A Dfns account
- A service account with permissions to read approvals and submit decisions. This is the Checker.
serviceAccountsCanApproveenabled on your organization (contact our )- A Dfns wallet for the Maker, tagged so the policy can target it
- A Dfns user listed as an additional approver on the policy
- Node.js v22+
- Testnet ETH in the Maker wallet for gas fees (use a Sepolia faucet)
Configuration
Set up environment variables
The
.env identifies the service account (the checker), not the maker. The maker is identified only by SENDER_WALLET_ID..env
| Variable | Description |
|---|---|
DFNS_API_URL | Dfns API base URL (api.dfns.io) |
DFNS_ORG_ID | Your Dfns organization ID. See how to find it. |
DFNS_AUTH_TOKEN | Auth token for the service account |
DFNS_CRED_ID | Credential ID for the service account signing key |
DFNS_PRIVATE_KEY | Service account private key (PEM) |
SENDER_WALLET_ID | Maker wallet — its pending requests will be evaluated |
POLICY_USER_ID | User listed as an additional approver on the policy |
CONTRACT_ADDRESS | Filled in after npm run deploy |
WHITELIST_ADDRESS | Address allowed as mint recipient |
Deploy and create the policy
Step 1: Deploy the stablecoin
.env as CONTRACT_ADDRESS.
Step 2: Tag the maker wallet
In the Dfns dashboard, add the tagautoreview to the Maker wallet (see walletTags in scripts/CreatePolicy.ts). The policy filter uses this tag to scope itself to that wallet.
Step 3: Create the approval policy
Wallets:Sign policy with AlwaysTrigger and an approval group of quorum 1, listing both the user (POLICY_USER_ID) and the service account as eligible approvers.
Trigger and review a transaction
Step 1: Broadcast a mint as the maker
Pending.
Step 2: List pending approvals
Step 3: Run the automated checker
- Calls
policies.listApprovals({ status: 'Pending' }) - Filters to approvals from
SENDER_WALLET_ID - Loads the contract ABI from the Hardhat artifact
- Calls
decodeFunctionDatafrom viem onrequestBody.data - Evaluates the rules and posts a decision via
policies.createApprovalDecision
npm run approvals:auto.
How the checker works
decodeFunctionData from viem reads the 4-byte function selector at the start of the call data, finds the matching entry in the ABI, and decodes the remaining bytes into typed arguments. The rules above are then plain TypeScript, no DSL to learn.
Polling vs. webhooks
The reference implementation pollslistApprovals from a CLI, which is simple to demo and run locally. For production, use a webhook on the policy.approval.pending event so the checker reacts the moment a request is created instead of on a polling interval.
Design considerations
Prefer abstaining over rejecting
A service-account rejection is final and irrevocable: it denies the entire approval with no override. The reference implementation rejects on every failure path so that denials are observable in the dashboard during a demo. In production, prefer abstaining when uncertain. Don’t post a decision, and let a human approver in the same group review the case. Reject only on conditions that are unambiguously malicious.Separate automation from initiation
Don’t use the same identity to both initiate transactions and approve them. The policy engine prevents self-approval wheninitiatorCanApprove is false. Keep maker and checker on distinct Dfns identities.
Pin the ABI
decodeFunctionData will silently match any function whose selector collides with the bytes you feed it. Always validate requestBody.to against an allowlist of contracts whose ABI you trust before decoding, and reject calls to unknown targets.
Monitor your automation
Track approval latency, escalation rates, and false positives. If the checker escalates too often, the rules may be too strict. If it never escalates, the checks may not be thorough enough.Adapting for your contract
Different contract. Replacecontracts/StableCoin.sol with your own and update the artifact path in SCApproveOrReject.ts. The ABI is the only thing the checker needs to decode calls.
More functions. Replace the single if (decoded.functionName === 'mint') block with a switch over the function names you want to allow, each with its own argument validation.
Off-chain data. Because the checker runs as TypeScript, you can call any HTTP API, read a database, or query other on-chain state before deciding. Common examples: AML/KYC lookups on the recipient, market-data sanity checks on a swap amount.
Long-lived runner. Run the checker as a worker (cron, AWS Lambda on a schedule, or a Kubernetes CronJob) polling listApprovals every few seconds, or behind a webhook handler.
Related
Manage policies via API
Implementation code for automated approvals
Define treasury policies
Spending limits and approval quorums
Apply compliance controls
KYT/AML screening integration
Automate payments
Service account payment workflows
