전송 전에 컴플라이언스 검사를 시뮬레이션하는 방법

integration intermediate

PCL 래핑 프록시로 향하는 호출을 정적으로 시뮬레이션해 사용자가 서명하기 전에 PCL이 적용 가능한 모든 정책을 평가하도록 하는 방법을 다루는 집중 가이드입니다. dApp 사용자 경험을 개선하고 가스 낭비를 막습니다.

마루에서는 사용자가 트랜잭션에 서명하고 보내기 전에 그 트랜잭션이 네트워크의 컴플라이언스 규칙을 통과할지 확인할 수 있습니다. 방법은 PCL 래핑 프록시로 향하는 평범한 정적 호출입니다. 프록시의 훅이 로직 컨트랙트에 위임하기 전에 IPcl.preCall을 호출하므로, 시뮬레이션된 호출도 전역 + 컨트랙트 범위 정책 평가를 모두 수행하고 사용자가 실제로 만나게 될 ReasonCode로 revert합니다. 가스를 쓰지도, 아무것도 기록하지도 않습니다. 이 가이드는 JavaScript/TypeScript 프론트엔드에서의 절차를 단계별로 설명합니다.

사전 요구사항

  • 사용자가 호출하는 PCL 래핑 프록시 주소를 알고 있을 것.
  • Ethers.js 또는 유사 라이브러리로 클라이언트 측을 설정해 둔 상태입니다.

1. 목표: 사전 검사

사용자가 규제된 토큰을 대량으로 전송하려는 상황을 가정해 봅시다. 트랜잭션을 제출했는데 PCL 정책(예: 전송 한도)으로 실패하면, 가스는 그대로 소비됩니다. 이는 사용자 경험을 해칩니다. 따라서 PCL 래핑 프록시를 대상으로 호출을 먼저 시뮬레이션하고 즉시 피드백을 제공해, 자금을 낭비하지 않고도 금액을 조정하거나 작업을 중단할 수 있게 합니다.

2. 컨트랙트 설정

프록시 주소로직 컨트랙트의 ABI(예: ERC20 토큰 ABI)를 바인딩한 컨트랙트 인스턴스가 필요합니다. 사용자가 실제로 호출하는 주소가 바로 이 프록시입니다. IPcl ABI도 함께 준비해 두세요. 시뮬레이션 실패 시 revert되는 값이 PCL의 타입 있는 ReasonCode이며, 이를 디코드하는 데 그 ABI가 필요합니다.
import { ethers } from 'ethers';
import PCL_ABI from './PclAbi.json';
import TOKEN_ABI from './TokenAbi.json';

const PCL_ADDRESS = '0x1000000000000000000000000000000000000005';
const TOKEN_PROXY = '0x...'; // 토큰 로직 앞에 놓인 PCL 래핑 프록시

// 'provider'는 ethers Provider 인스턴스라고 가정합니다
const pclContract = new ethers.Contract(PCL_ADDRESS, PCL_ABI, provider);   // 조회 + 에러 디코딩용
const tokenContract = new ethers.Contract(TOKEN_PROXY, TOKEN_ABI, provider); // 사용자가 호출하는 대상

3. 대상이 PCL 래핑 프록시인지 확인하기

정적 호출이 정책 평가를 수행하려면 호출 대상 주소가 실제로 PCL 훅을 갖고 있어야 합니다. IPcl.pclProxy(address)는 해당 컨트랙트의 레지스트리 엔트리를 반환하며, 등록된 PCL 프록시가 아니면 모든 필드가 0입니다. 따라서 kindUnspecified(0)이면 그 주소를 대상으로 한 시뮬레이션은 컨트랙트 범위 정책에 대해 아무것도 알려주지 않습니다. 이 확인은 트랜잭션마다가 아니라 로드 시 한 번만 하면 됩니다.
// PclProxyKind: 0 = Unspecified, 1 = Transparent, 2 = UUPS, 3 = Beacon
const entry = await pclContract.pclProxy(TOKEN_PROXY);

if (Number(entry.kind) === 0) {
  // 등록된 PCL 프록시가 아닙니다 — 컨트랙트 범위 정책은 평가되지 않습니다.
  // 실제 트랜잭션에는 전역 정책이 여전히 적용됩니다.
}

4. 정적 호출 수행

프록시의 메서드를 정적 호출로 시뮬레이션합니다. 트랜잭션을 만들지도 상태를 바꾸지도 않고 노드에서 실행되며, 훅은 그래도 동작합니다. 여기서 중요한 것은 from 주소를 반드시 지정하는 일입니다. PCL은 발신자 범위 정책을 principal에 대해 평가하고 훅이 이를 전달하므로, from 없이 시뮬레이션하면 엉뚱한 주체를 검사하게 됩니다.
// ethers v6 — v5의 contract.callStatic이 아니라 메서드의 .staticCall을 호출합니다
async function isTransferAllowed(userAddress, recipient, amount) {
  try {
    await tokenContract.transfer.staticCall(
      recipient,
      amount,
      { from: userAddress } // 가장 중요한 부분입니다!
    );
    console.log('시뮬레이션 통과 — 트랜잭션이 허용됩니다.');
    return true;
  } catch (error) {
    console.error('시뮬레이션 실패 — 트랜잭션이 차단됩니다.');
    // PCL 커스텀 에러는 error.data에 ABI 인코딩되어 있습니다. IPcl ABI로 디코드합니다.
    if (error.data) {
      console.error('ReasonCode:', pclContract.interface.parseError(error.data));
    }
    return false;
  }
}

// 사용 예:
isTransferAllowed(user.address, recipient, ethers.parseEther('50000'));
참고: <method>.staticCall(...) (ethers v6) 또는 JSON-RPC eth_call 사용합니다. ethers v5의 contract.callStatic.x(...)는 v6에서 제거되었으므로, 신규 dApp은 v6 형식을 사용합니다.

마무리

이 시뮬레이션 로직을 dApp에 통합하면 더 똑똑하고 반응이 빠른 사용자 인터페이스를 만들 수 있습니다. 가스가 소비되기 전에 PCL의 실시간 피드백을 활용해 '보내기' 버튼을 비활성화하거나, 경고 메시지를 표시하거나, 유효한 거래 금액을 제안할 수 있습니다.
ESC
검색어를 입력하세요