가스비?
Web3에서는 생소할수도 있는 가스비 라는 개념이 있다. 블록체인 네트워크에 특정 action(송금, 민팅 등)을 실행시키고, 검증시키는 댓가로 일정한 수수료(가스비)를 낸다. 보통 수수료하면 1,000원, 2,000원 정도를 생각할 수 있다. 하지만 블록체인, 특히 이더리움 같은 경우는 민팅시 사람들이 많이 몰려서 몇십만원 까지 수수료를 낸적도 있었다. 상품가격 보다 수수료가 더 비싸지는 기이한 현상이 발생한 것 이다.
아더디드 NFT를 구매하기 위해 ETH 가스비를 지불해야했는데, 구매 경쟁이 심해지면서 가스비가 최대 5ETH에 이르기도 하였음. 구매자들은 총 1억 2300만달러(약 1,550억원)를 가스비로 지불한것으로 알려졌다.
https://twitter.com/yugalabs/status/1520612364839661568?s=20&t=gfvmJTH-t-oB74ya74eTRQ
클레이튼 네트워크 같은 경우에는 가스비는 고정이 되어있어서 그나마 가스비 폭등에서 비교적 자유롭다 (22.06.17 기준 250ston). 그렇지만, UX 관점에서 볼때 가스비는 아직 많이 생소한것은 사실이다. 예를들어, 배달의민족에서 음식을 주문할때 우리가 명시적으로 배민 서버 비용을 매번 청구받지 않는것 처럼 말이다. 이에 클레이튼은 회사 혹은 개발 주체단에서 수수료를 대신 내 줄 수 있는 Fee delegation이라는 기능을 지원한다.
다음은 NFT 민팅을 예시로 작성된 코드다. caver-js를 사용한다.
구현
Client Side
먼저, 트랜잭션을 만들어준다
const senderTransaction = {
type: "FEE_DELEGATED_SMART_CONTRACT_EXECUTION",
from: walletAddress,
to: deployedAddress,
data: contract.methods
.mint(caver.utils.toBN(mintAmount))
.encodeABI(),
gas: 1000000,
value: caver.utils.toPeb((+mintAmount * tokenPrice).toString(), "peb"),
};
그다음 트랜잭션을 유저의 private key로 sign 해준다. kaikas 같은 경우는 sign 승인을 할 수 있는 팝업창이 뜰 것이다.
const { rawTransaction: senderRawTransaction } =
(await caver.klay.signTransaction(
senderTransaction
)) as RLPEncodedTrasactionWithRawTransaction<RLPEncodedTransaction>;
마지막으로 sign된 트랜잭션을 서버로 보내준다.
const { txHash: txHash } = (
await axios.post("/api/gas-station/", {
senderRawTransaction: senderRawTransaction,
})
).data;
console.log(txHash)
여기 까지가 클라이언트가 할 일이다. 이후 대납은 서버에서 이루어진다.
Server Side
서버에서는 트랜잭션을 사람이 sign 하거나 송금할 수 없기 때문에, 미리 인메모리 월렛에 private key를 넣어두고 request가 오면 자동으로 대납을 진행해준다. Node나 Lambda로 light하게 짜면된다.
인메모리 월렛에 대납용 계정 추가
const caver = new Caver("https://api.baobab.klaytn.net:8651");
caver.klay.accounts.wallet.add(
process.env.GAS_STATION_ADDRESS_PRIVATE_KEY || "",
process.env.GAS_STATION_ADDRESS
);
Client로부터 받은 transaction에 signing 후 송금
const { senderRawTransaction: senderRawTransaction } = request.body;
const { transactionHash: txHash } = await caver.klay.sendTransaction({
senderRawTransaction: senderRawTransaction,
feePayer: process.env.GAS_STATION_ADDRESS,
});
클라로 다시 response
return response.status(200).json({
txHash: txHash,
});
전체 코드
클라이언트
type RLPEncodedTrasactionWithRawTransaction<T> = Partial<T> & {
rawTransaction: string;
};
const caver = new Caver(window.klaytn);
const contract = caver.contract.create(ABI as AbiItem[], deployedAddress);
const senderTransaction = {
type: "FEE_DELEGATED_SMART_CONTRACT_EXECUTION",
from: walletAddress,
to: deployedAddress,
data: contract.methods
.mintMetaHuman(caver.utils.toBN(mintAmount))
.encodeABI(),
gas: 1000000,
value: caver.utils.toPeb((+mintAmount * tokenPrice).toString(), "peb"),
};
const { rawTransaction: senderRawTransaction } =
(await caver.klay.signTransaction(
senderTransaction
)) as RLPEncodedTrasactionWithRawTransaction<RLPEncodedTransaction>;
const { txHash: txHash } = (
await axios.post("/api/gas-station/", {
senderRawTransaction: senderRawTransaction,
})
).data;
console.log(txHash)
서버
// /api/gas-station.ts
const caver = new Caver("https://api.baobab.klaytn.net:8651");
caver.klay.accounts.wallet.add(
process.env.GAS_STATION_ADDRESS_PRIVATE_KEY || "",
process.env.GAS_STATION_ADDRESS
);
const { senderRawTransaction: senderRawTransaction } = request.body;
const { transactionHash: txHash } = await caver.klay.sendTransaction({
senderRawTransaction: senderRawTransaction,
feePayer: process.env.GAS_STATION_ADDRESS,
});
return response.status(200).json({
txHash: txHash,
});