Managing Contract Policies

advanced intermediate

Update, swap, remove, or hand over the PolicySets attached to a specific contract via the PCL precompile — without redeploying the contract or going through global governance.

Prerequisites

  • Deployed contract with a ContractPolicyConfig already registered
  • Wallet that holds the admin address recorded on that ContractPolicyConfig

Read the current configuration first

changeContractPolicies replaces the entire configuration in one shot — there is no per-policy delta API. Always fetch the existing config before modifying, so you can rebuild policies locally and preserve entries you don't intend to change.
import { createPublicClient, http } from "viem";

const PCL = "0x1000000000000000000000000000000000000005";
const publicClient = createPublicClient({ transport: http(process.env.MAROO_RPC) });

const current = await publicClient.readContract({
  address: PCL,
  abi: pclAbi,
  functionName: "contractPolicies",
  args: [tokenAddress],
});

console.log("admin:",    current.admin);
console.log("policies:", current.policies.length);
Warning: If you submit `changeContractPolicies` with an incomplete `policies` array you will drop the omitted PolicySets on-chain. Always read-then-write.

Add or replace a PolicySet

Rebuild the policies array locally with the entry appended, edited, or removed, then submit as the current admin. Set config.admin equal to yourself to keep authority.
import { encodeAbiParameters } from "viem";

// New OKRW transfer cap: 10,000,000 OKRW per transfer.
const limitBytes = encodeAbiParameters(
  [
    { type: "address", name: "easContract" },
    { type: "address", name: "indexContract" },
    { type: "bytes32", name: "schemaUid" },
    { type: "uint256", name: "transferLimitAmount" },
  ],
  [EAS_ADDR, INDEX_ADDR, schemaUID, 10_000_000n * 10n ** 18n],
);

const nextPolicies = [
  ...current.policies,
  {
    templateId: "OKRW_EAS_TRANSFER_LIMIT_POLICY",
    policy:     limitBytes,
    selector:   "0x",
  },
];

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: tokenAddress,
    admin:     current.admin,   // keep the current admin
    policies:  nextPolicies,
  }],
});

Hand over admin authority

There is no dedicated transfer method — handover happens through changeContractPolicies. As the current admin, submit the call with config.admin set to the new admin's address. After the transaction lands, only that new address can further modify or remove the contract's policies; any subsequent call from the old admin will revert with Unauthorized. Handover and policy edits are atomic in the same call, so a common pattern is to leave policies untouched while flipping the admin.
// Called by the CURRENT admin.
const newAdmin = "0xCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCc"; // replace before production

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: tokenAddress,
    admin:     newAdmin,             // handover: authority transfers
    policies:  current.policies,     // keep policies as-is
  }],
});

// From this point onward the old admin cannot manage tokenAddress's policies.
Note: Confirm the new admin address off-chain before submitting. Handover is not reversible unilaterally — only the new admin can hand authority back.

Clear all policies without releasing admin

removeContractPolicies clears the PolicySet[] for a contract but does not clear the admin binding. That's by design — the admin slot is not releasable through external calls. After removal, the same admin can call changeContractPolicies again to re-author policies or hand over authority.
// Called by the current admin. Clears all PolicySets for tokenAddress.
await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "removeContractPolicies",
  args: [tokenAddress],
});

// tokenAddress now has an empty policy list but still has the same admin.
// Only the GlobalPolicyConfig applies to it until new PolicySets are attached.
ESC
Type to search