testnet
GitHub

Building a Compliant ERC20 Token with PCL

intermediate integration 45 min

An end-to-end guide to deploying an ERC20 token on Maroo and using PCL to enforce KYC-based transfer restrictions via on-chain attestations.

What You Will Learn

  • How to set up a Hardhat project for Maroo.
  • How to use the Ethereum Attestation Service (EAS) to issue KYC attestations.
  • How to configure a global PCL policy to check for these attestations.
  • How to deploy a standard ERC20 contract.
  • How to verify that PCL correctly blocks transfers from non-attested accounts.

Prerequisites

  • A running local Maroo testnet.
  • Basic knowledge of Solidity and ERC20 tokens.
  • Funded accounts on your local testnet.

Tools Needed

HardhatNode.js 18+marood CLIMetaMask
In this tutorial, you will build a security token that only allows transfers between KYC-verified users. We will achieve this not by writing custom, non-standard Solidity code, but by leveraging Maroo's PCL module to enforce rules at the protocol level. This approach keeps your smart contracts simple and standard, while compliance is handled by the chain itself.
1

Step 1: Set Up EAS and Issue Attestations

First, we need an 'identity verifier' to issue attestations. On your local testnet, the EAS contracts are already deployed. We will use a script to define a simple KYC schema and issue an attestation to one of our test accounts.

Create a script scripts/issueAttestation.js.
scripts/issueAttestation.js javascript
const { EAS, SchemaEncoder } = require("@ethereum-attestation-service/eas-sdk");
const { ethers } = require("hardhat");

const EAS_CONTRACT_ADDRESS = "0x..."; // Get this from your testnet deployment

async function main() {
  const [deployer, kycUser, nonKycUser] = await ethers.getSigners();
  const eas = new EAS(EAS_CONTRACT_ADDRESS);
  await eas.connect(deployer);

  const schemaEncoder = new SchemaEncoder("bool kycVerified");
  const schemaUID = await eas.registerSchema(schemaEncoder.encode());

  console.log("New schema UID:", schemaUID);

  const encodedData = schemaEncoder.encodeData([{ name: "kycVerified", value: true, type: "bool" }]);
  const tx = await eas.attest({
    schema: schemaUID,
    data: {
      recipient: kycUser.address,
      expirationTime: 0,
      revocable: true,
      data: encodedData,
    },
  });

  const newAttestationUID = await tx.wait();
  console.log("New attestation UID:", newAttestationUID);
}

main().catch(console.error);
Tip: You will need the addresses of the deployed EAS and Indexer contracts on your testnet. These are typically found in the genesis file or deployment artifacts.
2

Step 2: Configure a Global PCL Policy

Now, we'll act as the PolicyAdmin to set a global policy that requires the attestation we just created. This is done via a governance proposal.
eas-policy-params.json json
{
  "eas_contract": "0x...",
  "index_contract": "0x...",
  "schema_uid": "YOUR_SCHEMA_UID_FROM_STEP_1"
}
terminal bash
PARAMS_B64=$(base64 -w 0 eas-policy-params.json)
proposal.json json
{
  "messages": [
    {
      "@type": "/maroo.pcl.v1.MsgSetGlobalPolicy",
      "authority": "POLICY_ADMIN_ADDRESS",
      "policies": [
        {
          "template_id": "EAS_POLICY",
          "parameters": "$PARAMS_B64"
        }
      ]
    }
  ],
  "title": "Enable Global KYC Check",
  "summary": "Enforce KYC for all transfers via EAS attestation.",
  "deposit": "1000000000000000000000aokrw"
}
terminal bash
marood tx gov submit-proposal proposal.json --from policy_admin_key
# ...vote on the proposal to pass it
3

Step 3: Deploy and Test the ERC20 Token

With the global policy active, we can now deploy a completely standard ERC20 token. We'll use an OpenZeppelin contract for this.
scripts/deploy.js javascript
const { ethers } = require("hardhat");

async function main() {
  const MyToken = await ethers.getContractFactory("MyToken"); // Standard OZ ERC20
  const token = await MyToken.deploy();
  await token.deployed();
  console.log("Token deployed to:", token.address);
}

main();

Conclusion

You have successfully implemented a compliant token on Maroo. Notice that we didn't write any custom logic in our Solidity contract. The compliance rules were applied externally by the chain's PCL module, demonstrating the power of separating application logic from regulatory enforcement.
Source: maroo
ESC
Type to search