How to Simulate Compliance Checks with runOnPcl
A focused guide on using the PCL's runOnPcl function to perform pre-flight transaction checks, improving dApp user experience and preventing wasted gas.
Prerequisites
- An understanding of how to encode function calldata.
- A client-side setup with Ethers.js or a similar library.
1. The Goal: Pre-flight Checks
Imagine a user wants to transfer a large amount of a regulated token. If they submit the transaction and it fails due to a PCL policy (like a transfer limit), they still pay for gas. This is a poor user experience. Our goal is to use
runOnPcl to check the transaction first and provide immediate feedback to the user, allowing them to adjust the amount or abort the action without wasting funds.2. Setting Up the Contracts
You'll need two contract instances in your client-side code: one for the PCL precompile and one for the target contract you want to interact with (e.g., an ERC20 token).
import { ethers } from 'ethers';
import PCL_ABI from './PclAbi.json';
import TOKEN_ABI from './TokenAbi.json';
const PCL_ADDRESS = '0x1000000000000000000000000000000000000005';
const TOKEN_ADDRESS = '0x...'; // The address of the token with policies
// Assume 'provider' is an ethers Provider instance
const pclContract = new ethers.Contract(PCL_ADDRESS, PCL_ABI, provider);
const tokenContract = new ethers.Contract(TOKEN_ADDRESS, TOKEN_ABI, provider); 3. Encoding the Transaction Calldata
The second argument to
runOnPcl is the encoded data for the function you want to simulate. You can get this from the contract's interface object.const recipient = '0x...';
const amount = ethers.utils.parseEther('50000'); // An amount that might fail
const calldata = tokenContract.interface.encodeFunctionData('transfer', [
recipient,
amount
]);
// calldata will be a hex string like '0xa9059cbb0000...' 4. Performing the Static Call
To simulate the transaction, we use a
static call. This executes the function on a node without creating a transaction or changing state. Crucially, we must provide the from address in the call options, as the PCL policies are evaluated against the sender.async function isTransferAllowed(userAddress, tokenAddress, calldata) {
try {
await pclContract.callStatic.runOnPcl(
tokenAddress,
calldata,
0, // The 'value' for the call, 0 for an ERC20 transfer
{ from: userAddress } // This is the most important part!
);
console.log('✅ Simulation passed. The transaction is allowed.');
return true;
} catch (error) {
console.error('❌ Simulation failed. Transaction would be blocked.');
// You can inspect the error to find the specific PCL revert reason
if (error.data) {
// The PCL precompile returns custom errors. You may need a helper to decode them.
console.error('Revert reason:', error.data);
}
return false;
}
}
// Usage:
isTransferAllowed(user.address, TOKEN_ADDRESS, calldata); Note: Using `callStatic` (or `eth_call` via JSON-RPC) is key. A regular call would attempt to create a state-changing transaction, which is not what we want for a simulation.