This guide explains the concept of Service Accounts within the DFNS ecosystem and demonstrates how they can be used to programmatically approve or reject smart contract operations based on custom on-chain data decoding.
In traditional systems, policies are often evaluated based on static rules (e.g., "Is the transaction amount under $1000?"). However, interacting with Web3 and Smart Contracts requires a deeper level of dynamic evaluation.
When a user initiates a transaction to a smart contract, the actual details of what that transaction is doing (who gets tokens, which admin function is called, etc.) are buried inside an ABI-encoded data payload.
A Service Account acts as an automated, programmatic "Checker" in a Maker/Checker workflow. It allows you to:
- Intercept a pending policy approval.
- Decode the complex on-chain transaction data (the ABI payload).
- Evaluate custom business logic against the decoded parameters.
- Decide autonomously to either
ApproveorDenythe transaction.
By granting a Service Account the right to approve transactions within a Policy, you create a powerful, automated middle-tier for risk management and compliance.
Tip
Production Best Practice
In a true Maker/Checker production environment, the initiatorCanApprove flag on the policy should be set to false, ensuring that the human initiating the transaction cannot bypass the automated checks enforced by the Service Account.
The script SCApproveOrReject.ts is a concrete implementation of an automated Service Account. It pulls pending approvals, unpacks the Ethereum transaction payload, applies business rules, and automatically registers a decision with the DFNS API.
Here is the step-by-step breakdown of its workflow:
The script leverages the DFNS SDK to query the policy engine for any approvals currently waiting in a Pending state.
const approvals = await dfnsApi.policies.listApprovals({
query: { status: 'Pending' }
});To understand what the transaction is trying to do, the script needs the "dictionary" for the smart contract. It loads the StableCoin.json artifact (generated by Hardhat) to retrieve the ABI (Application Binary Interface).
const artifactPath = path.join(__dirname, '../artifacts/contracts/StableCoin.sol/StableCoin.json');
const artifact = JSON.parse(fs.readFileSync(artifactPath, 'utf8'));
const { abi } = artifact;Before attempting to decode anything, the script first verifies that the transaction is actually destined for the correct smart contract address. If not, the transaction is rejected or ignored.
const toAddress = requestBody.to;
if (toAddress != '0x007cb8aafaa9eb5b411ebece99f1671a286d2ee8') {
console.log("This is the wrong smart contract address. Rejecting")
continue;
}This is where the magic happens. The script uses the viem library to take the raw hex string from requestBody.data and decode it using the previously loaded ABI.
const decoded = decodeFunctionData({
abi,
data: requestBody.data as `0x${string}`
});If the transaction was a call to the mint(address to, uint256 amount) function, decodeFunctionData transforms the unreadable hex into structured data:
decoded.functionName:"mint"decoded.args:["0x126b39aFd4c1027168bf936B68C4d011793E7609", 9000000n]
With the data decoded into usable TypeScript variables, the Service Account can now enforce complex business logic.
In this specific script, the rules are:
- Is it the
mintfunction? -
Is the destination address whitelisted? (e.g., must be
0x126b39aFd4c1027168bf936B68C4d011793E7609). -
Is the amount within safe limits? (e.g., amount must be
$\le$ 10,000,000).
if (decoded.functionName === 'mint') {
const [destinationAddress, amount] = decoded.args as [string, bigint];
// Rule 1 Check: Destination Address
if (destinationAddress != '0x126b39aFd4c1027168bf936B68C4d011793E7609') {
await reject(approval.id)
} else {
// Rule 2 Check: Amount Limit
if (amount <= 10000000) {
await approve(approval.id)
} else {
await reject(approval.id) // Too large!
}
}
}Finally, the script calls back to the DFNS API, acting as the Service Account user, to officially register the Approved or Denied decision on the policy.
await dfnsApi.policies.createApprovalDecision({
approvalId,
body: {
value: 'Approved', // or 'Denied'
reason: 'Verified transaction details with finance team'
}
})By combining DFNS Policy Approvals with ABI Decoding, SCApproveOrReject.ts demonstrates a robust, automated pipeline. It prevents malicious or erroneous smart contract interactions before they ever reach the blockchain, all without requiring manual human intervention for every single transaction.