본문으로 건너뛰기

Permissioned Assets와 통합하기

주의

Permissioned Asset Standard는 현재 Testnet에서 사용할 수 있다. 아직 Mainnet에는 live 상태가 아니다.

Permissioned Assets Standard (PAS)와 통합하려면 다음을 수행해야 한다.

  1. public struct MyApproval() has drop;을 사용해 approval witness를 정의한다.

  2. policy::new_for_currency로 정책을 만들고 액션별 required approval을 설정한다.

  3. &mut Request<...>를 받고 validation을 수행한 뒤 request.approve(MyApproval())을 호출하는 함수으로 approval logic을 빌드한다.

  4. 클라이언트가 programmable 트랜잭션 블록 (PTB)을 자동으로 빌드할 수 있도록 template 명령를 설정한다.

TypeScript 패키지를 통해 PAS를 사용할 수 있으며, GitHub repo에서 더 자세히 알아볼 수 있다.

approval witness 정의

approval witness는 type-level stamp 역할을 하는 drop이 있는 zero-sized struct이다. 각 witness는 contract가 부여할 수 있는 별도의 approval을 나타낸다.

/// Witness for approved transfers between accounts.
public struct MyTransferApproval() has drop;

/// Witness for approved clawbacks (issuer withdrawal).
public struct MyClawbackApproval() has drop;

액션마다 하나의 witness 타입을 정의해 해당 액션의 approval로 사용할 수 있다. 각 witness는 정책에 독립적으로 등록되며, 선택적으로 자체 template 명령를 가질 수 있다.

currency용 정책 설정

Policy<Balance<C>>는 각 액션 type에 어떤 approval이 필요한지 정의한다. currency ownership을 증명하려면 TreasuryCap<C>가 필요하다.

다음 예시는 패키지 안에서 정책을 생성한다.

let (mut policy, policy_cap) = policy::new_for_currency(
&mut namespace,
&mut treasury_cap,
true, // clawback_allowed
);

// Require MyTransferApproval witness for transfers
policy.set_required_approval<Balance<T>, MyTransferApproval>(&policy_cap, "send_funds");

// Require MyClawbackApproval witness for clawbacks
policy.set_required_approval<Balance<T>, MyClawbackApproval>(&policy_cap, "clawback_funds");

policy.share();

지원되는 액션은 "send_funds", "unlock_funds", "clawback_funds"이다.

approval logic 정의

approval 함수는 Request에 대한 mutable 레퍼런스를 받고 validation을 실행한 뒤 witness로 stamp한다.

transfer에는 예를 들어 KYC registry 같은 external check가 필요하다.

public fun approve_transfer(
registry: &KYCRegistry,
request: &mut Request<SendFunds<Balance<MY_COIN>>>,
) {
assert!(registry.users.contains(&request.data().recipient()), ENotKYCd);
request.approve(MyTransferApproval());
}

완전한 구현은 KYC example kyc_registry를 참조한다.

template 명령 설정

Template은 클라이언트-side automation을 위해 미리 빌드된 Move call 명령 structure를 온체인에 저장한다. SDK는 이를 통해 패키지 ID나 함수 서명을 hardcoding하지 않고 approval resolution에 필요한 올바른 Move call을 구성할 수 있다.

Template은 순수하게 offchain utility이다. onchain execution이나 security에는 영향을 주지 않는다. Template은 SDK가 어떤 Move call을 만들어야 하는지 설명한다. 이 덕분에 하나의 generic SDK가 구체적인 approval logic과 관계없이 어떤 PAS-managed 자산도 지원할 수 있다.

Template은 전송이 발생할 때 PAS SDK가 다음을 알아야 하므로 유용하다.

  1. approval을 위해 어떤 contract를 호출해야 하는지

  2. 그 contract가 어떤 argument를 기대하는지

  3. 어떤 타입 파라미터를 사용해야 하는지

Template은 이 정보를 approval witness type으로 keying된 온체인 Command 객체로 저장한다.

issuer가 approval logic을 upgrade하면(예: TransferApproval에서 TransferApprovalV2로) template을 온체인에서 업데이트한다. 모든 클라이언트는 클라이언트-side update, redeployment, 지갑 및 frontend 간 coordination 없이 새 resolution path를 자동으로 사용한다.

template 설정

template 명령은 호출할 approval 함수를 설명하는 MoveCall이다. ptb::move_call로 빌드하고 set_template_command를 통해 등록한다.

public fun set_template_command<A: drop>(
templates: &mut Templates,
_: internal::Permit<A>,
command: Command,
)

Template은 요청 approval에 사용되는 witness type인 A를 key로 사용한다. 이로써 각 approval 타입마다 정확히 하나의 template 명령이 있는 one-to-one mapping이 만들어진다.

예시

KYC example kyc_registry의 approval 함수가 다음과 같다고 하자.

public fun approve_transfer(
registry: &KYCRegistry,
request: &mut Request<SendFunds<Balance<MY_COIN>>>,
) {
assert!(registry.users.contains(&request.data().recipient()), ENotKYCd);
request.approve(MyTransferApproval());
}

이를 호출하는 방법을 설명하는 template을 다음과 같이 등록한다.

let type_name = type_name::with_defining_ids<MY_COIN>();

let cmd = ptb::move_call(
type_name.address_string().to_string(), // package ID
"kyc_registry", // module
"approve_transfer", // function
vector[
ptb::object_by_id(object::id(registry)), // KYCRegistry shared object
ptb::ext_input<PAS>("request"), // the request (resolved by SDK)
],
vector[], // type arguments
);

templates.set_template_command(internal::permit<MyTransferApproval>(), cmd);

template의 공통 argument type

ConstructorDescription
ptb::ext_input<T>(name)SDK가 offchain에서 해결하는 custom input이며, type T로 namespacing된다.
ptb::object_by_id(id)ID로 offchain에서 해결되는 객체이다.
ptb::receiving_object_by_id(id)ID로 offchain에서 해결되는 receiving 객체이다.
ptb::pure(value)BCS-encoded pure value이다.

전체 ptb constructor 목록(system shorthand 및 fully-해결d ref 포함)은 PTB 패키지를 참조한다.

지원되는 ext_input 이름

다음은 PAS SDK가 PTB construction 시 해결하는 custom input 이름이다. ptb::ext_input<PAS>(name)을 사용할 때 PAS type(pas::templates에서 제공)이 namespace를 제공한다.

NameResolves to
"request"approval 중인 active Request 객체.
"policy"managed 자산의 Policy 객체.
"sender_account"송신자의 Account 객체.
"receiver_account"receiver의 Account 객체.

클라이언트가 template을 사용하는 방식

  1. 클라이언트가 Templates 공유 객체에서 template을 읽는다.

  2. template은 필요한 Move call(패키지, 모듈, 함수, arguments)을 설명한다.

  3. 클라이언트는 PTB를 빌드한다: 요청 생성, template 명령(approval), 해결.

이를 통해 generic 지갑과 frontend는 구체적인 컴플라이언스 contract detail을 알지 못해도 compliant 전송을 실행할 수 있다.