1. Install ehthers zkSync
npm install ethers zkSync
2. zkSync 란?
zkSync 는 EVM 을 확장하기 위한 레이어 2 솔루션. ZKP 를 이용한 빠르고 저렴한 거래처리 가능
ZKP (Zero Knowledge Proof) 는 뭔데?
어떤 정보를 드러내지 않고 특정 주장이나 사실이 참임을 증명하는 기술
블록체인에서는 거래내역을 보여주지 않고 거래내역이 유효함을 증명할 수 있다.
zkSync 를 통해 빠르고 저렴한 거래처리를 적용할 수 있다!
Provider 생성
alice.js, bob.js 를 zkSync 로 연결하기 위해서는 util.js 에서 zkSync Provider를 생성해야한다.
// utils.js
async function getZkSyncProvider(zksync, networkName) {
let zkSyncProvider;
try {
zkSyncProvider = await zksync.getDefaultProvider(networkName);
} catch (error) {
console.log('Unable to connect to zkSync.');
console.error(error);
}
return zkSyncProvider;
}
3. zkSync 계정 만들기
zkSync 와 이더리움을 연결하려면 계정과 지갑에 대해 알아야한다. 따라서, zkSync 의 지갑 계정을 생성해 이더리움과 연결해보자.
async function getZkSyncProvider (zksync, networkName) {
let zkSyncProvider
try {
zkSyncProvider = await zksync.getDefaultProvider(networkName)
} catch (error) {
console.log('Unable to connect to zkSync.')
console.log(error)
}
return zkSyncProvider
}
async function getEthereumProvider (ethers, networkName) {
let ethersProvider
try {
ethersProvider = new ethers.getDefaultProvider(networkName)
} catch (error) {
console.log('Could not connect to Rinkeby')
console.log(error)
}
return ethersProvider
}
async function initAccount (rinkebyWallet, zkSyncProvider, zksync) {
const zkSyncWallet = await zksync.Wallet.fromEthSigner(rinkebyWallet, zkSyncProvider)
return zkSyncWallet
}
위 코드의 결과로 zkSyncWallet 을 생성할 수 있다.
4. zkSync 인증 확인하기
zkSync 계정을 생성했다면 인증 여부를 확인해야한다. 지갑의 key 가 존재한다면 key 가 singing key에 등록이 되어있는지 확인 후 changePubkey 에 저장한다.
async function registerAccount (wallet) {
console.log(`Registering the ${wallet.address()} account on zkSync`)
if(!await wallet.isSigningKeySet()){
if(await wallet.getAccountId() === undefined){
throw new Error('Unknown account')
}
const changePubkey = await wallet.setSigningKey()
await changePubkey.awaitReceipt()
}
}
5. zkSync 에 재화를 넣어보자
Priority Operations
Transactions
zkSync에서 transaction 이 사용되는 원리
zkSync 에 트랜잭션을 보낼때 Promise 를 사용하게 된다. 트랜잭션의 작업을 믿는다면 따로 블록이 생성될 때까지 기다릴 필요없다.
zkSync 트랜잭션은 SNARK 증명 방식으로 검증하고, 이더리움 스마트 컨트랙트에 등록될 때까지 10분 소요된다.
async function depositToZkSync(zkSyncWallet, token, amountToDeposit, ethers){
const deposit = await zkSyncWallet.depositToSyncFromEthereum({
depositTo: zkSyncWallet.address(),
token: token,
amount: ethers.utils.parseEther(amountToDeposit)
})
try{
await deposit.awaitReceipt()
}catch(error){
console.log('Error while awaiting confirmation from the zkSync operators.')
console.log(error)
}
}
- deposit 객체를 만들기 위해 이더리움 wei 단위로 변경해야한다.
- awaitReceipt 함수를 이용해 트랜잭션이 정상 작동한지 확인 한다.
6. zkSync 에서 애셋 옮기기
애셋을 옮기기 위해서는 두 가지 과정으로 나눌 수 있다.
- syncTransfer 함수를 통해 zkSync 지갑과 수취인의 주소, 애셋 정보 및 fee 를 전달할 수 있다.
- awaitReceipt 를 통해 정상 작동했는지 확인할 수 있다.
zkSync에서 전송 작업의 정밀도는 제한되어 있으므로 전송량은 5바이트 길이의 부동 소수점
지불한 수수료는 2바이트 길이의 부동 소수점 표현 으로 패키징해야한다.
async function transfer (from, toAddress, amountToTransfer, transferFee, token, zksync, ethers) {
const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(ethers.utils.parseEther(amountToTransfer))
const closestPackableFee = zksync.utils.closestPackableTransactionFee(ethers.utils.parseEther(transferFee))
const transfer = await from.syncTransfer(
{
to:toAddress,
token: token,
amount : closestPackableAmount,
fee:closestPackableFee
}
)
const transferReceipt = await transfer.awaitReceipt()
console.log('Got transfer receipt.')
console.log(transferReceipt)
}
7. Transfer Fee
Transfer fee 를 계산하는 방법을 배우자
fee 의 종류
- off-chain fee
- on-chain fee
- 이더리움에서 SNARK 를 검증하는데 드는 비용이다. 가스 가격에 따라 달라지기에 가변적이다.
async function getFee(transactionType, address, token, zkSyncProvider, ethers){
const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token)
return ethers.utils.formatEther(feeInWei.totalFee.toString())
}
- getTransactionFee 로 구한 wei 는 매우 길기 때문에 formatEther 함수로 처리해준다.
8. Withdraw to Ethereum
세 가지 과정으로 Withdraw 할 수 있다.
- closestPackableAmount 를 계산
- wallet.withdrawFromSyncToEthereum 으로 withdraw를 계산
- Receipt 검증 완료
async function withdrawToEthereum (wallet, amountToWithdraw, withdrawalFee, token, zksync, ethers) {
const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(ethers.utils.parseEther(amountToWithdraw))
const closestPackableFee = zksync.utils.closestPackableTransactionFee(ethers.utils.parseEther(withdrawalFee))
const withdraw = await wallet.withdrawFromSyncToEthereum({
ethAddress : wallet.address(),
token : token,
amount : closestPackableAmount,
fee : closestPackableFee
})
await withdraw.awaitVerifyReceipt()
console.log('ZKP verification is complete')
}
9. Account Balances
계정의 Balance 에는 두가지가 존재한다.
- commit balance
- 거래 내역을 zkSync 의 스마트 컨트랙트에 포함시킨다.
- 즉시 사용 가능
- 이더리움 메인넷에 기록되지만 검증되지 않았다.
- verify balance
- 블록을 최종 검증 상태로 확정
- SNARK 로 블록을 검증
- 최종적으로 보장된 verify 금액
async function displayZkSyncBalance (wallet, ethers){
const state = await wallet.getAccountState()
if(state.committed.balances.ETH){
console.log(`Commited ETH balance for ${wallet.address()}: ${ethers.utils.formatEther(state.committed.balances.ETH)}`)
}else{
console.log(`Commited ETH balance for ${wallet.address()}: 0`)
}
if(state.verified.balances.ETH){
console.log(`Verified ETH balance for ${wallet.address()}: ${ethers.utils.formatEther(state.verified.balances.ETH)}`)
}else{
console.log(`Verified ETH balance for ${wallet.address()}: 0`)
}
}
10. 가게주인 밥 - 블록체인과 연결
위에서 만든 utils.js 를 이용해 클라이언트와 연결해보자
(async () => {
const ethers = require('ethers')
const zksync = require('zksync')
const utils = require('./utils')
// Start here
const zkSyncProvider = await utils.getZkSyncProvider(zksync, process.env.NETWORK_NAME)
const ethersProvider = await utils.getEthereumProvider(ethers, process.env.NETWORK_NAME)
})()
- zkSyncProvider, ethersProvider 을 util.js 에서 불러와야 한다.
11. 가게주인 밥 - 밥의 지갑 주소를 가져오다
const bobRinkebyWallet = new ethers.Wallet(process.env.BOB_PRIVATE_KEY, ethersProvider)
console.log(`Bob's Rinkeby address is: ${bobRinkebyWallet.address}`)
console.log(`Bob's initial balance on Rinkeby is: ${ethers.utils.formatEther(await bobRinkebyWallet.getBalance())}`)
const bobZkSyncWallet = await utils.initAccount(bobRinkebyWallet, zkSyncProvider, zksync)
- 밥에게 필요한 지갑주소와 zkSync 지갑 주소를 가져온다.
12. 잔액 업데이트
zkSync 에는 잔액이 업데이트 됐음을 알리는 메커니즘이 없기 때문에 setInterval 을 통해 잔액을 주기적으로 최신화해야한다.
process.on('SIGINT', () => {
console.log('Disconnecting')
// Disconnect
process.exit()
})
setInterval(async () => {
// Call the `utils.displayZkSyncBalance` function
await utils.displayZkSyncBalance(bobZkSyncWallet, ethers);
console.log('---')
}, SLEEP_INTERVAL)
13. 계좌 등록
상점을 사용할 앨리스의 계좌를 등록해야한다.
앨리스가 상점을 방문한 이후의 시나리오는 다음과 같다.
- 앨리스는 zkSync 계좌를 이용해 이더리움을 배포하려고한다.
- 퍼블릭 키를 등록해 zkSync 로 거래할 수 있게 하려고 한다.
- zkSync 를 이용해 밥과 거래를 할 것 이다.
- 이더리움 코인으로 zkSync 에서 이더리움으로 옮겨 남은 금액을 확인해야한다.
(async () => {
const ethers = require('ethers')
const zksync = require('zksync')
const utils = require('./utils')
const token = 'ETH'
const amountToDeposit = '0.05'
const amountToTransfer = '0.02'
const amountToWithdraw = '0.002'
const zkSyncProvider = await utils.getZkSyncProvider(zksync, process.env.NETWORK_NAME)
const ethersProvider = await utils.getEthereumProvider(ethers, process.env.NETWORK_NAME)
console.log('Creating a new Rinkeby wallet for Alice')
const aliceRinkebyWallet = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY, ethersProvider) // Account #78
console.log(`Alice's Rinkeby address is: ${aliceRinkebyWallet.address}`)
const aliceInitialRinkebyBalance = await aliceRinkebyWallet.getBalance()
console.log(`Alice's initial balance on Rinkeby is: ${ethers.utils.formatEther(aliceInitialRinkebyBalance)}`)
console.log('Creating a zkSync wallet for Alice')
const aliceZkSyncWallet = await utils.initAccount(aliceRinkebyWallet, zkSyncProvider, zksync)
console.log('Depositing')
// Start here
await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, ethers)
await utils.displayZkSyncBalance(aliceZkSyncWallet, ethers)
await utils.registerAccount(aliceZkSyncWallet)
})()
14. zkSync 로 결제해보자
utils.getFee, utils.transfer 함수를 이용해 결제 시나리오를 만들어보자.
const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, ethers)
await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee,token,zksync,ethers)
15. withdraw
결제와 마찬가지로 withdraw 도 구현할 수 있다.
const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token,zkSyncProvider, ethers)
await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token,zksync,ethers)