PCL ReasonCodes

component compliance

PCL이 트랜잭션을 거절할 때 발생시키는 타입이 있는 오류 목록입니다. 지갑과 SDK는 이 코드들을 기준으로 사용자 경험을 구성합니다.

PCL이 거절할 때는 항상 IPcl에 정의된 타입이 있는 Solidity 오류 중 하나를 함께 반환합니다. 지갑과 dApp 코드는 revert 페이로드를 IPcl ABI로 디코드하고, 자유 문자열이 아니라 오류 이름과 인자를 기준으로 UX를 구성해야 합니다. 코드는 크게 세 그룹으로 나뉩니다. 정책 위반 코드(컴플라이언스 규칙 실패), 구성 코드(잘못된 템플릿 id, 파라미터 형식 오류, 구조 제약 위반), 인프라 코드(권한 없는 호출자, ABI 디코드 실패)입니다.

정책 위반 ReasonCodes

사용자의 트랜잭션이 활성 컴플라이언스 규칙에 걸릴 때 나타나는 오류입니다. 내장 정책 템플릿과 1:1로 대응합니다. 일부는 사용자 액션(attestation 완료, 금액 축소, 윈도 리셋 대기)으로 해소할 수 있으므로, 지갑은 코드를 감지해 알맞은 해결 경로를 제시해야 합니다.

ReasonCode트리거지갑 UX
InDenylist(address sender)발신자가 DENYLIST_POLICY 목록에 있는 경우종결 상태 — "이 주소는 거래할 수 없습니다"를 표시하고 재시도하지 않습니다
VolumeBelowMinLimit(uint256 minLimit, uint256 value)트랜잭션 단위 VOLUME_POLICY 하한 미달더 큰 금액을 제안합니다
VolumeAboveMaxLimit(uint256 maxLimit, uint256 value)트랜잭션 단위 VOLUME_POLICY 상한 초과더 작은 금액 또는 분할 전송을 제안합니다
ExceededPeriodicVolume(uint256 maxLimit, uint256 value, uint256 resetAt)PERIODIC_VOLUME_POLICY(또는 OKRW/EAS 변형)의 윈도 한도 소진. resetAt은 현재 윈도가 끝나는 unix 타임스탬프"한도 도달 — resetAt에 초기화"를 카운트다운과 함께 표시합니다
ReachedLimitOfNonEAS(uint256 maxLimit, uint256 value)EAS 게이트가 걸린 볼륨 정책의 미인증 상한 초과attestation 완료 또는 더 작은 금액을 제안합니다
EasAttestationRequired(address sender)EAS_POLICY 계열 — 발신자에게 attestation이 필요한 경우attestation / 온보딩 흐름으로 안내합니다
EasNoAttestationReceived(address sender)EAS_POLICY 계열 — 발신자의 attestation을 찾지 못한 경우동일한 온보딩 흐름으로 안내합니다
EasAttestationRevoked(address sender)EAS_POLICY 계열 — 발신자의 attestation이 폐기된 경우재온보딩 또는 에스컬레이션으로 안내합니다(폐기는 의도된 조치입니다)
EasAttestationExpired(address sender)EAS_POLICY 계열 — 발신자의 attestation이 만료된 경우재attestation 흐름으로 안내합니다
EasAttestationLookupFailed(address sender)EAS_POLICY 계열 — 평가 중 attestation을 읽지 못한 경우일시적 오류로 처리하고, 일반 오류 표시 후 재시도합니다
AgentKeeperRequired()AGENT_OKRW_TRANSFER_LIMIT_POLICY 평가 시 에이전트 keeper를 사용할 수 없는 경우운영자 측 문제 — 일반 오류로 표시합니다
ExceededAgentTransferLimit(uint256 maxLimit, uint256 value)AGENT_OKRW_TRANSFER_LIMIT_POLICY 에이전트별 상한 초과더 작은 금액을 제안합니다
AgentTransferLimitMetadataInvalid(string reason)AGENT_OKRW_TRANSFER_LIMIT_POLICY — 에이전트의 한도 메타데이터를 파싱할 수 없는 경우운영자 측 문제이며 사용자 오류가 아닙니다
AnyOfRejected(bytes[] childReverts)Or quantifier를 가진 LogicalPolicy의 모든 자식이 거절한 경우. 배열에 각 자식의 원본 revert 페이로드가 담깁니다자식 페이로드를 같은 디코더로 디코드해 가장 실행 가능한 코드를 우선 표시합니다
import { decodeErrorResult } from "viem";

try {
  await walletClient.writeContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: "transfer",
    args: [recipient, 10_000_000n * 10n ** 18n], // aokrw 단위 1천만 OKRW
  });
} catch (err: any) {
  const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
  console.log("reason:", decoded.errorName, decoded.args);
  // 예: ExceededPeriodicVolume { maxLimit, value, resetAt }
}

구성 ReasonCodes

관리자(전역 또는 컨트랙트)가 잘못된 PolicySet을 제출하거나 부적절한 레지스트리 조작을 시도할 때 나타납니다. 외부 dApp은 ContractPolicyConfig를 등록할 때 이 코드들을 볼 수 있습니다. LogicalPolicyChildrenEmpty 이하의 행은 조합 정책 트리의 구조 검사이며, pcl-composite-policies에서 자세히 설명합니다.

ReasonCode트리거
InvalidPolicyTemplate(string input)템플릿 등록 검증 실패 — 등록하려는 템플릿 정의가 유효하지 않은 경우
DuplicatedPolicyTemplate(string templateId)이미 존재하는 템플릿 id를 재등록하려는 경우
PolicyTemplateInUse()활성 PolicySet이 아직 참조 중인 템플릿을 제거하려는 경우
UnknownPolicyType(string templateId)제출된 PolicySettemplateId가 등록된 어떤 정책 타입과도 매핑되지 않는 경우 — 알 수 없는 PolicySet.templateId는 이 오류로 표면화됩니다
UnknownPolicyConfigType()제출된 정책 config가 GlobalPolicyConfigContractPolicyConfig 어느 쪽으로도 디코드되지 않는 경우
PolicyAlreadyRegistered(string policy)등록 충돌 — 대상 컨트랙트의 admin 슬롯이 이미 바인딩되어 있는데 호출자가 그 admin이 아닌 경우. 사용 중인 템플릿에 대한 등록 보호 검사에서도 발생합니다
PolicyNotRegistered(string policy)편집 중인 config에 등록되지 않은 정책을 지목한 경우
PolicyCannotBeNested(string policyType)중첩이 허용되지 않는 정책(예: OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY)을 LogicalPolicy 또는 ForEachPolicy의 자식으로 배치한 경우. 조합 트리에는 중첩 평가를 지원하는 정책 타입만 넣을 수 있습니다
InvalidParameter(bytes input)PolicySet의 ABI 인코딩 파라미터 blob이 템플릿 파라미터 struct로 디코드되지 않는 경우
InvalidSelector(bytes input)PolicySet의 selector가 빈 값도 유효한 4바이트 selector도 아닌 경우
LogicalPolicyChildrenEmpty()LogicalPolicy의 자식 목록이 빈 채로 제출된 경우
LogicalPolicyChildNil(uint256 index)LogicalPolicyindex 위치 자식이 nil인 경우
ForEachChildAbsent()ForEachPolicy가 자식 정책 없이 제출된 경우
ForEachSubjectUnspecified()ForEachPolicy의 subject 필드가 지정되지 않은 경우
UnknownForEachSubject(uint8 subject)ForEachPolicy의 subject 값이 알려진 subject 종류가 아닌 경우
QuantifierUnspecified()LogicalPolicy가 quantifier 없이 제출된 경우
ChildSelectorNotEmpty()중첩 자식 PolicySet이 비어 있지 않은 selector를 가진 경우 — selector는 바깥 PolicySet에만 둡니다
MaxDepthExceeded(uint8 maxDepth)조합 정책 트리가 maxDepth보다 깊게 중첩된 경우
CannotEmpty(string field)config의 필수 필드가 비어 있는 경우
// 중첩이 금지된 정책을 LogicalPolicy 트리에 넣으면
// PolicyCannotBeNested(policyType)으로 revert됩니다.
//
// bytes4(keccak256("PolicyCannotBeNested(string)"))
// 클라이언트에서는 다른 IPcl 커스텀 오류와 동일하게 처리합니다.
//   decodeErrorResult({ abi: pclAbi, data: err.data })
// → { errorName: "PolicyCannotBeNested", args: ["OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY"] }

인프라 ReasonCodes

컴플라이언스 규칙 위반도 정책 config 오류도 아닌, 프리컴파일 자체의 오용을 알리는 오류입니다.

ReasonCode트리거
Unauthorized()호출자가 이 메서드를 호출할 권한이 없는 경우 (예: 정책 admin이 아닌 계정이 setGlobalPolicies를 호출, 컨트랙트 admin이 아닌 계정이 changeContractPolicies를 호출)
InvalidAddress(string input)20바이트 주소여야 하는 인자가 그렇지 않은 경우
InvalidCall()프리컴파일이 잘못된 calldata로 호출된 경우
InvalidStructType(string got)ABI 디코드된 struct가 해당 인자 슬롯의 기대 타입과 다른 경우
AbiDecodeFailed(string reason)인자 blob 자체를 ABI 디코드할 수 없는 경우
JSONMarshal() / JSONUnmarshal()체인 측 (역)직렬화가 실패한 경우 — 대체로 SDK 결함을 의미합니다
// setGlobalPolicies는 정책 admin만 호출할 수 있으며, 다른 호출자는 Unauthorized()로 revert됩니다.
try {
  await walletClient.writeContract({
    address: PCL,
    abi: pclAbi,
    functionName: "setGlobalPolicies",
    args: [{ policies: [] }],
  });
} catch (err: any) {
  const { errorName } = decodeErrorResult({ abi: pclAbi, data: err.data });
  if (errorName === "Unauthorized") {
    // "이 작업은 정책 admin 권한이 필요합니다" 형태로 표시합니다.
  }
}

디코드 패턴

모든 PCL 오류는 타입이 있는 Solidity 커스텀 오류입니다. @maroo-chain/contracts에서 얻은 IPcl ABI로 4바이트 selector와 뒤이은 바이트를 디코드하며, 문자열 매칭은 사용하지 않습니다. AnyOfRejected는 자식 revert 페이로드 배열을 담고 있으므로 동일한 디코더로 재귀 처리하면 Or 조합의 각 분기가 실패한 이유를 표면화할 수 있습니다. 코드 식별자는 안정적입니다. 새 정책 템플릿이 출시될 때 새 코드가 추가되며 기존 코드의 이름은 바뀌지 않으므로, 과거 트랜잭션 실패도 그대로 해석할 수 있습니다. 정식 전체 목록은 IPcl.solerror … 선언입니다.
import { decodeErrorResult } from "viem";
import { pclAbi } from "@maroo-chain/contracts/abis/IPcl";

function explain(revertData: `0x${string}`): string {
  const { errorName, args } = decodeErrorResult({ abi: pclAbi, data: revertData });
  if (errorName === "AnyOfRejected") {
    const childReverts = args[0] as `0x${string}`[];
    return `AnyOfRejected [${childReverts.map(explain).join(", ")}]`;
  }
  return `${errorName}(${args.map(String).join(", ")})`;
}
소스: maroo
ESC
검색어를 입력하세요