How to Simulate Compliance Checks with runOnPcl

integration intermediate

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.
Source: maroo
ESC
Type to search