본문으로 건너뛰기

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로 policy를 만들고 action별 required approval을 설정한다.

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

  4. client가 programmable transaction block (PTB)을 자동으로 빌드할 수 있도록 template command를 설정한다.

TypeScript package를 통해 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;

action마다 하나의 witness type을 정의해 해당 action의 approval로 사용할 수 있다. 각 witness는 policy에 독립적으로 등록되며, 선택적으로 자체 template command를 가질 수 있다.

currency용 policy 설정

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

다음 예시는 package 안에서 policy를 생성한다.

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();

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

approval logic 정의

approval function은 Request에 대한 mutable reference를 받고 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 command 설정

Template은 client-side automation을 위해 미리 빌드된 Move call command structure를 온체인에 저장한다. SDK는 이를 통해 package ID나 function signature를 hardcoding하지 않고 approval resolution에 필요한 올바른 Move call을 구성할 수 있다.

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

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

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

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

  3. 어떤 type parameter를 사용해야 하는지

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

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

template 설정

template command는 호출할 approval function을 설명하는 MoveCall이다. ptb::move_call로 빌드하고 set_template_command를 통해 등록한다.

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

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

예시

KYC example kyc_registry의 approval function이 다음과 같다고 하자.

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에서 resolve하는 custom input이며, type T로 namespacing된다.
ptb::object_by_id(id)ID로 offchain에서 resolve되는 object이다.
ptb::receiving_object_by_id(id)ID로 offchain에서 resolve되는 receiving object이다.
ptb::pure(value)BCS-encoded pure value이다.

전체 ptb constructor 목록(system shorthand 및 fully-resolved ref 포함)은 PTB package를 참조한다.

지원되는 ext_input 이름

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

NameResolves to
"request"approval 중인 active Request object.
"policy"managed asset의 Policy object.
"sender_account"sender의 Account object.
"receiver_account"receiver의 Account object.

client가 template을 사용하는 방식

  1. client가 Templates shared object에서 template을 읽는다.

  2. template은 필요한 Move call(package, module, function, arguments)을 설명한다.

  3. client는 PTB를 빌드한다: request 생성, template command(approval), resolve.

이를 통해 generic wallet과 frontend는 구체적인 compliance contract detail을 알지 못해도 compliant transfer를 실행할 수 있다.