수수료 대납 (Fee Delegation, FD)는 transaction을 발생시킨 이용자가 해당 transaction을 수수료를 지불하는게 아닌 다른 이용자가 낼 수 있도록 하는 기능입니다.
TxTypeFeeDelegated*
종류의 transaction의 경우 수수료를 from이 아닌 fee payer가 지불을 하게 되어 있어있습니다. 그렇기 때문에 fee payer
의 주소와 서명이 해당 transaction에 포함이 되어야합니다. 아래의 다양한 종류의 transaction을 참고 하세요.
TxTypeFeeDelegatedValueTransfer
TxTypeFeeDelegatedValueTransferWithMemo
TxTypeFeeDelegatedSmartContractDeploy
TxTypeFeeDelegatedSmartContractExecution
TxTypeFeeDelegatedAccountUpdate
TxTypeFeeDelegatedCancel
부분적인 수수료 대납은 Transaction type명에 뒤에
WithRatio
를 붙이면 됩니다.
수수료 대납의 예시
시나리오
0xALICE
라는 주소인 사용자 Alice는 0xBOB
주소를 갖는 Bob에서 1 KLAY를 전송하고자 합니다. 이때 Alice의 잔고가 정확히 1 KLAY라서 Alice는 0xFRED
라는 주소를 가진 Fred에게 수수료를 대신 지불하도록 하려고 합니다.
Transaction의 전달 과정
1. Alice
Alice는 1 KLAY를 Bob에게 전송하며 대납이 가능하도록 대납용 transaction으로 서명하여 생성합니다. 이 경우에는 KLAY를 전송하는 대납 transaction으로 TxTypeFeeDelegatedValueTrasfer
을 사용합니다. Alice의 transaction 은 아래와 같습니다.
{
"from" : "0xALICE",
"to": "0xBOB",
"type": "TxTypeFeeDelegatedValueTransfer",
"value": "0xde0b6b3a7640000", // 1 KLAY
"signatures": [ { "R": "0x...", "S": "0x...", "V": "0x..." } ],
"gas" : "0x2dc6c0", // 3,000,000
"gasPrice": "0x5d21dba00" // 25 Ston
...
}
Alice가 서명을 끝낸 다음, 이 transaction을 EN에 전송하지 않고 그 대신 Alice는 Fred에게 전송합니다. transaction을 encode하여 raw data를 다양한 통신방법으로 전송 가능합니다.
Alice ---> Fred
2. Fred
Fred가 Alice로 부터 transaction을 받게 되면 해당 transaction에 Fred는 자신의 address를 fee payer
로 추가하고 자신의 서명을 추가하게 됩니다. 이를 EN에 전송하여 transaction을 전파시킵니다.
이때 Fred는 이미 Alice가 해당 transaction에 서명을 하였기 때문에 transaction의 내용을 변경하진 못합니다. 즉, Fred는 해당 transaction에 자신의 서명을 추가하는 것만 가능합니다.
Alice ---> Fred ---> [[ Klaytn ]]
3. Klaytn
Fred가 클레이튼 네트워크에 전파한 transaction은 다음과 같습니다.
{
"from" : "0xALICE",
"to": "0xBOB",
"feePayer": "0xFRED",
"feePayerSignatures": [ { "R": "0x...", "S": "0x...", "V": "0x..." } ],
"type": "TxTypeFeeDelegatedValueTransfer",
"value": "0xde0b6b3a7640000", // 1 KLAY
"signatures": [ { "R": "0x...", "S": "0x...", "V": "0x..." } ],
"gas" : "0x2dc6c0", // 3,000,000
"gasPrice": "0x5d21dba00" // 25 Ston
...
}
위의 예시에서는 nonce와 같은 나머지 데이터는 맞다고 가정을 한다면 클레이튼 노드들은 해당 transaction에 대해서 검증/실행을 아래 단계로 수행하게 됩니다.
- Transaction 종류를 확인합니다.
- 만약,
TxTypeFeeDelegated
종류이면from
과signatures
가 일치하는지 그리고feePayer
와feePayerSignatures
가 일치하는지 서명을 확인 합니다. - 모든 항목이 맞다면 "from"인 fred의 계정으로 부터
gas * gasPrice
만큼의 KLAY를 차감하고 transaction을 실행하게 됩니다. (만약 Fred가 충분한 KLAY가 없다면 transaction을 실행되지 않습니다.) - Transaction이 실행되며 이후 남은 수수료 (
(gas - gasUsed) * gasPrice
)는 다시 Fred의 계정으로 환불됩니다.
이렇게 Fred가 대납하는 Alice의 transaction은 블록에 담기게 되며 Bob은 1 KLAY를 Alice로 부터 받을 수 있게 됩니다.
FD in Javascript
이제 Klaytn의 Caver SDK를 통해 어떻게 구현을 하는지 살펴보겠습니다. 아래 코드를 참조해주세요.
const Caver = require('caver-js');
const caver = new Caver('https://api.baobab.klaytn.net:8651/');
let sender = caver.klay.accounts.wallet.add("0xALICE_PRIV_KEY");
let payer = caver.klay.accounts.wallet.add("0xFRED_PRIV_KEY");
let { rawTransaction: senderRawTransaction } = await caver.klay.accounts.signTransaction({
type: 'FEE_DELEGATED_VALUE_TRANSFER',
from: sender.address,
to: '0xBOB',
gas: '3000000',
value: caver.utils.toPeb('1', 'KLAY'),
}, sender.privateKey);
// Alice ---> Fred
// : Alice somehow delivers raw tx to Fred
let { rawTransaction: finalTx } = await caver.klay.accounts.signTransaction({
senderRawTransaction: senderRawTransaction,
feePayer: payer.address
}, payer.privateKey);
caver.klay.sendSignedTransaction(finalTx).then((err, receipt) => {
console.log(receipt);
});
위의 코드는 앞서서 설명한 transaction의 전달과정과는 다른 점이 있습니다. 이 코드는 Fred가 Alice를 전적으로 신뢰한다는 가정하에 Fred의 대납을 위한 private key를 Alice가 가지고 있다는 가정으로 만들어진 코드 입니다.
Alice
위의 코드에서 처음에 transaction을 작성하고 private key로 서명을 합니다. caver-js에서는 transaction type의 경우 TxTypeFeeDelegatedValueTransfer
가 아닌 영문 대문자로 구성된 FEE_DELEGATED_VALUE_TRANSFER
를 사용합니다.
자세한 내용은 링크를 참조 하십시요.
Fred
두번재는 Fred의 private key를 이용해서 feePayer
를 추가하고 transaction에 서명을 하게 됩니다. 최종적으로 모든 서명이 완료된 transaction은 sendSignedTransaction
을 통해 클레이튼 네트워크에 전송하게 됩니다.
정리
추가적인 자세한 내용을 위해서는 이 링크를 추가로 참조 하십시요.
FD in Java
아래의 코드는 먼저 설명드린 Javascript 코드와 동일한 기능을 구현한 Caver.java 코드 입니다.
더 자세한 내용은 이 링크를 참조 하십시요.
/* Omitted package statement and import statements for brevity */
public class FeeDelegatedValueTransfer {
private static final BigInteger GAS_LIMIT = BigInteger.valueOf(3000000L);
private static final BigInteger GAS_PRICE = BigInteger.valueOf(25000000000L);
private static DefaultBlockParameter BLK_PARAM = DefaultBlockParameterName.LATEST;
private static int CHAIN_ID;
private static Caver caver;
private static KlayCredentials senderCredential;
private static KlayCredentials payerCredential;
@BeforeClass
public static void setup() {
// Baobab setting
CHAIN_ID = ChainId.BAOBAB_TESTNET;
caver = Caver.build("https://api.baobab.klaytn.net:8651/");
senderCredential = KlayCredentials.create("0xALICE_PRIV_KEY");
payerCredential = KlayCredentials.create("0xFRED_PRIV_KEY");
}
@Test
public void feeDelegatedValueTransferTest() throws IOException {
BigInteger nonce = caver.klay().getTransactionCount(senderCredential.getAddress(), BLK_PARAM).send().getValue();
TxTypeFeeDelegatedValueTransfer tx = TxTypeFeeDelegatedValueTransfer
.createTransaction(
nonce,
GAS_PRICE,
GAS_LIMIT,
"0xBOB",
BigInteger.ONE,
senderCredential.getAddress()
);
String rawTx = tx.sign(senderCredential, CHAIN_ID).getValueAsString();
FeePayerManager feePayerManager = new FeePayerManager.Builder(caver, payerCredential)
.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(caver, 1000, 10))
.setChainId(CHAIN_ID)
.build();
KlayTransactionReceipt.TransactionReceipt transactionReceipt = feePayerManager.executeTransaction(rawTx);
}
}