[Tech] RoleBasedAccount Tutorial
What is RoleBasedAccount?
EVM 기반의 Account는 크게 2종류로 나뉘어져 있습니다.
1. EOA ( External Owned Account)
2. CA (Contract Account)
EOA는 기본적으로 유저가 사용하는
외부 소유 계정(privkey와 address가 존재하는) 어카운트이고
CA는 code를 내포하고 있는 컨트랙트 어카운트를 뜻합니다.
지난 시간 우리는 Caver, Web3/klaytn lib를 이용하여
클레이튼의 수수료 대납 서비스를 잠깐 다루어 봤습니다.
지난 포스팅
이를 통하여 서비스 제공자는
유저의 native token(klay)가 없이도 대신 대납하여
tx을 전송할 수 있는 예제를 다루어 보았는데요,
수수료 대납이라는 기능을 조금 더 스마트하게 사용해 보도록 합시다.
현재까지 예제로 작성한 fee delegate 워크 플로우는 아래와 같습니다.
1.caller(sender)가 전송할 input data를 생성하고 이에 대한 Sign을 거치고
2.FeePayer가 수수료 대납을 위한 1번에 대한 값을 한번더 Sign후에 Tx전송 합니다.
매우 심플 합니다만, 만약 대규모의 서비스를 제공하여야 하거나
조금 더 자유도 높은 FeePayed 관리를 하고자 하면 어떻게 해야할까요?
FeePayer의 다수 계정이 필요한 상황이 생겨난다면
서비스 제공자 입장에서는 다수의 FeePayer가 보유한 Klay를
매번 체크하고 이에 대해 관리해야 하는 관리소요가 들어갈 수 있습니다.
물론 대납기능만으로도 아주 좋은 서비스를 제공할 수 있으나
완벽히 아름답지는 않습니다.
그렇다면.
하나의 FeePayer Account로 FeePayer를 대신 호출할 수 있는
어카운트 설정을 해주는건 어떨까요?
그렇게 된다면
실제 대납해야하는 대납계정 1개와
대납을 호출할 대리대납호출계정 다수를 관리 하게 되며
서비스를 제공하며 일정에 대한 어느정도 커스터마이징도 가능 하겠습니다.
이러한 기능 역시 klaytn network에서
어카운트에 대한 role을 부여함으로써 가능 합니다.
AccountKeyRoleBased (klaytn docs)
실제 대납하는 어카운트 key에
대리호출 role 부여하고자 하는 월렛의 pubKey만 추가한다면
대리호출 어카운트로도 수수료대납을 이용할 수 있습니다.
코드를 본다면 조금 더 쉽습니다.
// 실제 대납되는 어카운트
const originFeePayer = "0xPrivKey";
// 대납 호출을 위한 어카운트
const delegateFeePayer = "0xPrivKey";
// STEP 1
// origin Fee Payer Account의 delegateFeePayer 정보 업데이트
const grantRoleUpdate = asnyc () => {
//대납 호출을 위한 어카운트의 PubKey를 추출합니다.
//caver에는 pubKey에 대한 추출기능이 없으므로 @klaytn/ethers-ext를 사용합시다.
const pubKey = new ethers.utils.SigningKey(delegateFeePayer).compressedPublicKey;
let tx = {
type: TxType.AccountUpdate,
from: originWallet.address,
gasLimit: 1000000,
key: {
type: AccountKeyType.RoleBased,
keys: [
// RoleFeePayer
{
type: AccountKeyType.Public,
key: pubKey,
},
],
},
};
let sentTx = await originWallet.sendTransaction(tx);
console.log("updateAccount", sentTx);
let receipt = await sentTx.wait();
console.log("receipt", receipt);
}
실제 대납되는 어카운트 originFeePayer에
대납 호출을 위한 delegateFeePayer 어카운트 정보를 추가하여
네트워크에 업데이트 하는 트랜잭션을 발생 시킵니다.
이렇게 한다면 앞으로 delegateFeePayer Account로도
대납 호출을 이용하여 originFeePayer를 통한 서비스 제공이 가능합니다.
그 다음 실제로 어떻게 호출을 하면 될까요?
const sentTx = async (to, amount) => {
const createWallet = await new ethers.Wallet.createRandom().connect(provider);
// ethers/klaytn extension wallet instance
const caller = new Wallet(createWallet.privateKey, provider);
const delegateFeeWallet = new Wallet(
originWallet.address, // 실제 대납하는 어카운트의 어드레스
delegateFeePayer, // 대리호출 하는 어카운트의 privKey
provider
);
const techTokenInstance = new ethers.Contract(
TECH_TOKEN_ADDRESS,
TECH_TOKEN_ABI,
provider
);
const param = techTokenInstance.interface.encodeFunctionData("mint", [
to,
amount,
]);
let tx = {
type: TxType.FeeDelegatedSmartContractExecution,
from: caller.address,
to: TECH_TOKEN_ADDRESS,
value: 0,
input: param,
gasLimit: 100000,
};
tx = await caller.populateTransaction(tx);
console.log(tx);
const senderTxHashRLP = await caller.signTransaction(tx);
console.log("senderTxHashRLP", senderTxHashRLP);
tx = delegateFeeWallet.decodeTxFromRLP(senderTxHashRLP);
console.log("tx", tx);
// backend sign
const sentTx = await delegateFeeWallet.sendTransactionAsFeePayer(tx);
console.log("sentTx", sentTx);
const receipt = await sentTx.wait();
console.log("receipt", receipt);
};
이 부분에서 많이 아리송할 수 있습니다.
const delegateFeeWallet = new Wallet(
originWallet.address, // 실제 대납하는 어카운트의 어드레스
delegateFeePayer, // 대리호출 하는 어카운트의 privKey
provider
);
이미 네트워크에는 originFeePayer Account에 delegateFeePayer에 대한
롤은 부여되어 있으므로 Wallet instance를 생성할시에
delegateFeePayer의 Address가 아닌 originFeePayer의 address를 적습니다.
이후 예제 코드에 따라 정상 작동함을 확인할 수 있습니다.
이번 klaytn에서 파일럿 프로그램을 시행하고 있는
Gas Fee Delegation Program 현재 이러한 Role Grant방식으로
시행 운영 되고 있으며, 서비스 제공자 builder의 경우에도
Key관리에 조금 더 유연한 관리 방안을 도출할 수 있습니다.
예제 깃헙 링크