IPcl.registerContractPolicies
registerContractPolicies(ContractPolicyConfig calldata policy) external Legacy first-time registration of a ContractPolicyConfig. Binds policy.admin as the PCL admin of policy._contract and stores the initial policy set. This entrypoint is deprecated: when the admin slot is already bound, only the currently bound admin may call it — and the call then performs a full re-registration (the policy sets are replaced and the slot is set to policy.admin, which also lets the current admin hand the slot over). A caller who is not the bound admin reverts with PolicyAlreadyRegistered. Prefer changeContractPolicies for all new code.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
policy | ContractPolicyConfig | ✓ | The config to register: (address _contract, address admin, PolicySet[] policies). admin is written into the contract's admin slot and becomes the only key allowed to call changeContractPolicies / removeContractPolicies afterward. |
Returns
void No return value. The transaction reverts on any validation or authorization failure.
Errors
| Code | Name | Description |
|---|---|---|
PolicyAlreadyRegistered | PolicyAlreadyRegistered | Reverts when the contract's admin slot is already bound and the caller is not that admin (the check is keyed on the caller, not on policy.admin). Contracts deployed via deployPclProxy have their admin slot bound to the deploy caller at deploy time — a subsequent registerContractPolicies from any other account hits this error. |
UnknownPolicyType | UnknownPolicyType | Reverts when any PolicySet.templateId does not decode to a policy type known on this network. |
InvalidParameter | InvalidParameter | Reverts when PolicySet.policy bytes do not decode into the expected parameter struct for the template. |
Examples
Legacy registration for a non-proxied contract
Use registerContractPolicies only when IPcl.contractPolicies(target).admin is the zero address. For any proxy created through deployPclProxy, the slot is already bound at deploy time and this call is not needed.
import {
IPcl, ContractPolicyConfig, PolicySet, DenylistPolicy
} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
IPcl pcl = IPcl(0x1000000000000000000000000000000000000005);
address[] memory blocked = new address[](1);
blocked[0] = 0x000000000000000000000000000000000000dEaD; // replace before production
PolicySet[] memory policies = new PolicySet[](1);
policies[0] = PolicySet({
templateId: "DENYLIST_POLICY",
policy: abi.encode(DenylistPolicy({ addresses: blocked })),
selector: ""
});
// Unbound slot: binds `admin`. If the slot is already bound, only the
// current admin may call this (and it performs a full re-registration).
pcl.registerContractPolicies(ContractPolicyConfig({
_contract: 0x71C7656EC7ab88b098defB751B7401B5f6d8976F, // replace
admin: msg.sender,
policies: policies
})); Choosing register vs change from a client
Portable client logic: read contractPolicies(target).admin; if it is the zero address, the slot is unbound and legacy registerContractPolicies still applies. Otherwise use changeContractPolicies.
import { createPublicClient, createWalletClient, http, zeroAddress } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const publicClient = createPublicClient({
transport: http("https://rpc-testnet.maroo.io"),
});
async function attachPolicies(target: `0x${string}`, config: any) {
const existing = await publicClient.readContract({
address: PCL,
abi: pclAbi,
functionName: "contractPolicies",
args: [target],
});
const fnName = existing.admin === zeroAddress
? "registerContractPolicies" // legacy path (deprecated)
: "changeContractPolicies"; // proxy-deployed or already-bound path
return walletClient.writeContract({
address: PCL,
abi: pclAbi,
functionName: fnName,
args: [config],
});
}