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.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 PCL policies are evaluated against the sender.// ethers v6 — call .staticCall on the method, not the v5 contract.callStatic
async function isTransferAllowed(userAddress, tokenAddress, calldata) {
try {
await pclContract.runOnPcl.staticCall(
tokenAddress,
calldata,
0n, // 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.');
// PCL custom errors are ABI-encoded in error.data; decode with the IPcl ABI.
if (error.data) {
console.error('Revert reason:', error.data);
}
return false;
}
}
// Usage:
isTransferAllowed(user.address, TOKEN_ADDRESS, calldata); Note: Use `<method>.staticCall(...)` (ethers v6) or `eth_call` via JSON-RPC. Ethers v5's `contract.callStatic.x(...)` was removed in v6 — newly built dApps should use the v6 form.