--- tags: it 鐵人賽 30 天, Web 3, ethereum --- # 從以太坊白皮書理解 web 3 概念 - Day22 ## Learn Solidity - Day 14 - Advanced zkSync Concept 今天將會透過 [Lession 18 - Advanced zkSync Concept](https://cryptozombies.io/en/lesson/18) 來了解 透過 zkSync 如何做到 ERC20 token 交換。 ## ERC20 交換 假設要透過 zkSync 做 ERC20 Token 做交換 這邊會以 USDT 作為範例 首先需要先取得 Rinkeby 鏈上的 USDT token 取得流程如下 1. 直接瀏覽到 [USDT Contract Address](https://rinkeby.etherscan.io/token/0x3b00ef435fa4fcff5c209a37d1f3dcff37c705ad) 2. 按下 Connect to Web3 連結,然後選擇要使用的錢包 3. 然後選擇 Write Cotnract 選項 ![](https://i.imgur.com/otyozzW.png) 4. 需要 mint (鑄造) 至少 6 USDT, mint 需要兩個參數如下: 1. _to: 代表要使佣 Rinkeby address 2. _amount: 代表要鑄造的數量 這邊填入 6000000 5. 按下 Write 6. 接著會看到 Metamask 彈出。選擇 Confirm 首先比需要先設定好 src/alice.js 來操作 USDT ### 實作 alice.js 1. 改變 token 值為 'USDT' 2. 改變 amoutToDeposit 值為 '6.0' 3. 改變 amoutToTransfer 與 amountToWithdraw 值為 '2.0' 更新如下 ```javascript= (async () => { const ethers = require('ethers') const zksync = require('zksync') const utils = require('./utils') const token = 'USDT' // Update this line const amountToDeposit = '6.0' // Update this line const amountToTransfer = '2.0' // Update this line const amountToWithdraw = '2.0' // Update this line 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) 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') await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, ethers) await utils.displayZkSyncBalance(aliceZkSyncWallet, ethers) await utils.registerAccount(aliceZkSyncWallet) console.log('Transferring') const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee, token, zksync, ethers) console.log('Withdrawing') const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token, zksync, ethers) })() ``` ## 調整單位更新功能 之前使用 ethers.util.formatEther 來對 ETH 單位做處理 但這邊需要使用 TokenSet 來做這件事 ### TokenSet zkSync 提供 TokenSet 這個類別來做單位轉換的功能 為了方便使用會使用以下語法把 zkSyncProvider.tokenSet 重新存入 const tokenSet ```javascript= const tokenSet = zkSyncProvider.tokenSet ``` 可以使用以下語法把一般表示式轉為 BigNumber ```javascript= const amountAsBigNumber = tokenSet.parseToken(token, amountInHumanReadableForm) ``` 可以使用以下語法把 BigNumber 轉為一般單位 ```javascript= const amountInHumanReadableForm = tokenSet.formatToken(token, amountAsBigNumber) ``` ### 讀取 Alice 的 USDT balance 需要使用 zksync.Wallet 物件的 getEthereumBalance 來取得 USDT balance 語法如下 ```javascript= const yourUSDTBalance = await yourZkSyncWallet.getEthereumBalance(token) ``` ### 實作 1. 刪除讀取原本讀取 ETH 的邏輯 2. 移出 ethers.utils.formatEthter 語法 3. 加入 const tokenSet = zksyncProvider.tokenSet 4. 使用 getEthereumBalance 語法讀出 USDT balance 5. 顯示 USDT balance 更新如下 ```javascript= (async () => { const ethers = require('ethers') const zksync = require('zksync') const utils = require('./utils') const token = 'USDT' const amountToDeposit = '6.0' const amountToTransfer = '2.0' const amountToWithdraw = '2.0' 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) console.log(`Alice's Rinkeby address is: ${aliceRinkebyWallet.address}`) // 2. Cut this line console.log('Creating a zkSync wallet for Alice') const aliceZkSyncWallet = await utils.initAccount(aliceRinkebyWallet, zkSyncProvider, zksync) // 3. Instantiate the `tokenSet` class const tokenSet = zkSyncProvider.tokenSet // 4. Retrieve Alice's initial USDT balance on Rinkeby const aliceInitialRinkebyBalance = await aliceZkSyncWallet.getEthereumBalance(token) // 5. Display Alice's balance console.log(`Alice's initial balance on Rinkeby is: ${tokenSet.formatToken(token, aliceInitialRinkebyBalance)}`) console.log('Depositing') await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, ethers) await utils.displayZkSyncBalance(aliceZkSyncWallet, ethers) await utils.registerAccount(aliceZkSyncWallet) console.log('Transferring') const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee, token, zksync, ethers) console.log('Withdrawing') const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token, zksync, ethers) })() ``` ## 實作 approve ERC20 Token transfer 1. 呼叫 aliceZkSyncWallet.approveERC20TokenDeposits(token) 更新如下 ```javascript= (async () => { const ethers = require('ethers') const zksync = require('zksync') const utils = require('./utils') const token = 'USDT' const amountToDeposit = '6.0' const amountToTransfer = '2.0' const amountToWithdraw = '2.0' 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) console.log(`Alice's Rinkeby address is: ${aliceRinkebyWallet.address}`) console.log('Creating a zkSync wallet for Alice') const aliceZkSyncWallet = await utils.initAccount(aliceRinkebyWallet, zkSyncProvider, zksync) const tokenSet = zkSyncProvider.tokenSet const aliceInitialRinkebyBalance = await aliceZkSyncWallet.getEthereumBalance(token) console.log(`Alice's initial balance on Rinkeby is: ${tokenSet.formatToken(token, aliceInitialRinkebyBalance)}`) // Start here await aliceZkSyncWallet.approveERC20TokenDeposits(token) console.log('Depositing') await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, ethers) await utils.displayZkSyncBalance(aliceZkSyncWallet, ethers) await utils.registerAccount(aliceZkSyncWallet) console.log('Transferring') const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee, token, zksync, ethers) console.log('Withdrawing') const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token, zkSyncProvider, ethers) await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token, zksync, ethers) })() ``` ## 修正所有 ethers 的功能改用 tokenSet 1. utils.depositToZkSync 帶入以下參數 * aliceZkSyncWallet * token * amountToDeposit * tokenSet 2. utils.displayZkSyncBalance 帶入以下參數 * aliceZkSyncWallet * tokenSet 3. utils.getFee 帶入以下參數 * 'Transfer' or 'Withdraw' * aliceRinkebyWallet.address * token * zkSyncProvicer * tokenSet 4. utils.transfer 帶入以下參數 * aliceZkSyncWallet * process.env.BOB_ADDRESS * amountToTransfer * transferFee * token * zksync * tokenSet 5. utils.withdrawToEthereum 帶入以下參數 * aliceZkSyncWallet * amountToWithdraw * withdrawalFee * token * zksync * tokenSet 更新如下 ```javascript= (async () => { const ethers = require('ethers') const zksync = require('zksync') const utils = require('./utils') const token = 'USDT' const amountToDeposit = '6.0' const amountToTransfer = '2.0' const amountToWithdraw = '2.0' 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) console.log(`Alice's Rinkeby address is: ${aliceRinkebyWallet.address}`) console.log('Creating a zkSync wallet for Alice') const aliceZkSyncWallet = await utils.initAccount(aliceRinkebyWallet, zkSyncProvider, zksync) const tokenSet = zkSyncProvider.tokenSet const aliceInitialRinkebyBalance = await aliceZkSyncWallet.getEthereumBalance(token) console.log(`Alice's initial balance on Rinkeby is: ${tokenSet.formatToken(token, aliceInitialRinkebyBalance)}`) await aliceZkSyncWallet.approveERC20TokenDeposits(token) console.log('Depositing') await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, tokenSet) // Update this line await utils.displayZkSyncBalance(aliceZkSyncWallet, tokenSet) // Update this line await utils.registerAccount(aliceZkSyncWallet) console.log('Transferring') const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, tokenSet) // Update this line await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee, token, zksync, tokenSet) // Update this line console.log('Withdrawing') const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token, zkSyncProvider, tokenSet) // Update this line await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token, zksync, tokenSet) // Update this line })() ``` ## 更新 depositToZkSync 功能 1. 修改 depositToSync 改用 tokenSet 來轉換 amountToDeposit 到 BigNumber 2. zkSyncWallet.depositToSyncFromEthereum 只需要一個參數 物件型態 需要設定 amount 屬行值為 tokenSet.parseToken(token, amountToDeposit) 更新如下 ```javascript= 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 { // eslint-disable-next-line new-cap 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 } 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() } console.log(`Account ${wallet.address()} registered`) } //1. On the next line, replace the last parameter (`ethers`) with `tokenSet` async function depositToZkSync (zkSyncWallet, token, amountToDeposit, tokenSet ) { const deposit = await zkSyncWallet.depositToSyncFromEthereum({ depositTo: zkSyncWallet.address(), token: token, amount: tokenSet.parseToken(token, amountToDeposit) //2. Call the `tokenSet.parseToken` function }) try { await deposit.awaitReceipt() } catch (error) { console.log('Error while awaiting confirmation from the zkSync operators.') console.log(error) } } 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) } async function getFee(transactionType, address, token, zkSyncProvider, ethers) { const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token) return ethers.utils.formatEther(feeInWei.totalFee.toString()) } 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') } 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`) } } module.exports = { getZkSyncProvider, getEthereumProvider, depositToZkSync, registerAccount, displayZkSyncBalance, transfer, withdrawToEthereum, getFee, initAccount } ``` ## 更新 displayZkSyncBalance 功能 透過 wallet.getAccountState 可以讀取到 state 物件 語法如下 ```javascript= const state = await wallet.getAccountState() ``` 假設把 state 列印出來會發現結果如下 ```javascript= { address: '0xc26f2adeeebbad73f25329ffa12cd3889429b5b6', committed: { balances: { ETH: '99891300000000000', USDT: '241896200' }, nonce: 5, pubKeyHash: 'sync:de9de11bdad08aa1cdc2beb5b2b7c7f29c10f079' }, depositing: { balances: {} }, id: 83, verified: { balances: { ETH: '99891300000000000', USDT: '235896200' }, nonce: 5, pubKeyHash: 'sync:de9de11bdad08aa1cdc2beb5b2b7c7f29c10f079' } } ``` ### 實作 displayZkSyncBalance 1. 修改 ethers 參數為 tokenSet 2. 宣告 const 變數 committedBalances = state.committed.balances 3. 宣告 const 變數 verifiedBalances = state.verified.balances 4. 使用 for .. in 迴圈列印出 committedBalances 內所有值語法如下 ```javascript= for (const property in committedBalances) { console.log(`Committed ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, committedBalances[property])}`) } ``` 5. 使用 for .. in 迴圈列印出 verifiedBalances 內所有值語法如下 ```javascript= for (const property in verifiedBalances) { console.log(`Committed ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, verifiedBalances[property])}`) } ``` 更新如下 ```javascript= 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 { // eslint-disable-next-line new-cap 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 } 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() } console.log(`Account ${wallet.address()} registered`) } async function depositToZkSync (zkSyncWallet, token, amountToDeposit, tokenSet) { const deposit = await zkSyncWallet.depositToSyncFromEthereum({ depositTo: zkSyncWallet.address(), token: token, amount: tokenSet.parseToken(token, amountToDeposit) }) try { await deposit.awaitReceipt() } catch (error) { console.log('Error while awaiting confirmation from the zkSync operators.') console.log(error) } } 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) } async function getFee(transactionType, address, token, zkSyncProvider, ethers) { const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token) return ethers.utils.formatEther(feeInWei.totalFee.toString()) } 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') } // 1. On the next line, replace the last parameter (`ethers`) with `tokenSet` async function displayZkSyncBalance (wallet, tokenSet) { const state = await wallet.getAccountState() // 2. Continue here const committedBalances = state.committed.balances const verifiedBalances = state.verified.balances for (const property in committedBalances) { console.log(`Commited ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, committedBalances[property])}`) } for (const property in verifiedBalances) { console.log(`Verified ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, verifiedBalances[property])}`) } } module.exports = { getZkSyncProvider, getEthereumProvider, depositToZkSync, registerAccount, displayZkSyncBalance, transfer, withdrawToEthereum, getFee, initAccount } ``` ## 更新 transfer function 1. 修改 transfer 參數 ethers 為 tokenSet 2. 改用 tokenSet.parseToken 來把 amountToTransfer 還有 fee 改成 BigNumber 更新如下 ```javascript= 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 { // eslint-disable-next-line new-cap 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 } 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() } console.log(`Account ${wallet.address()} registered`) } async function depositToZkSync (zkSyncWallet, token, amountToDeposit, tokenSet) { const deposit = await zkSyncWallet.depositToSyncFromEthereum({ depositTo: zkSyncWallet.address(), token: token, amount: tokenSet.parseToken(token, amountToDeposit) }) try { await deposit.awaitReceipt() } catch (error) { console.log('Error while awaiting confirmation from the zkSync operators.') console.log(error) } } // 1. On the next line, replace the last parameter (`ethers`) with `tokenSet` async function transfer (from, toAddress, amountToTransfer, transferFee, token, zksync, tokenSet) { // 2. Update the following two lines of code const closestPackableAmount = zksync.utils.closestPackableTransactionAmount( tokenSet.parseToken(token, amountToTransfer)) const closestPackableFee = zksync.utils.closestPackableTransactionFee( tokenSet.parseToken(token, 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) } async function getFee(transactionType, address, token, zkSyncProvider, ethers) { const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token) return ethers.utils.formatEther(feeInWei.totalFee.toString()) } 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') } async function displayZkSyncBalance (wallet, tokenSet) { const state = await wallet.getAccountState() const commitedBbalances = state.committed.balances const verifiedBalances = state.verified.balances for (const property in commitedBbalances) { console.log(`Commited ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, commitedBbalances[property])}`) } for (const property in verifiedBalances) { console.log(`Verified ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, verifiedBalances[property])}`) } } module.exports = { getZkSyncProvider, getEthereumProvider, depositToZkSync, registerAccount, displayZkSyncBalance, transfer, withdrawToEthereum, getFee, initAccount } ``` ## 更新 withdrawToEthereum 1. 修改 withdrawToEthereum 參數 ethers 為 tokenSet 2. 改用 tokenSet.parseToken 來把 amountToWithdraw, withdrawalFee 轉換成 BigNumber 更新如下 ```javascript= 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 { // eslint-disable-next-line new-cap 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 } 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() } console.log(`Account ${wallet.address()} registered`) } async function depositToZkSync (zkSyncWallet, token, amountToDeposit, tokenSet) { const deposit = await zkSyncWallet.depositToSyncFromEthereum({ depositTo: zkSyncWallet.address(), token: token, amount: tokenSet.parseToken(token, amountToDeposit) }) try { await deposit.awaitReceipt() } catch (error) { console.log('Error while awaiting confirmation from the zkSync operators.') console.log(error) } } async function transfer (from, toAddress, amountToTransfer, transferFee, token, zksync, tokenSet) { const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(tokenSet.parseToken(token, amountToTransfer)) const closestPackableFee = zksync.utils.closestPackableTransactionFee(tokenSet.parseToken(token, 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) } async function getFee(transactionType, address, token, zkSyncProvider, ethers) { const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token) return ethers.utils.formatEther(feeInWei.totalFee.toString()) } // 1. On the next line, replace the last parameter (`ethers`) with `tokenSet` async function withdrawToEthereum (wallet, amountToWithdraw, withdrawalFee, token, zksync, tokenSet) { // 2. Update the following two lines of code const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(tokenSet.parseToken(token, amountToWithdraw)) const closestPackableFee = zksync.utils.closestPackableTransactionFee(tokenSet.parseToken(token, withdrawalFee)) const withdraw = await wallet.withdrawFromSyncToEthereum({ ethAddress: wallet.address(), token: token, amount: closestPackableAmount, fee: closestPackableFee }) await withdraw.awaitVerifyReceipt() console.log('ZKP verification is complete') } async function displayZkSyncBalance (wallet, tokenSet) { const state = await wallet.getAccountState() const commitedBbalances = state.committed.balances const verifiedBalances = state.verified.balances for (const property in commitedBbalances) { console.log(`Commited ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, commitedBbalances[property])}`) } for (const property in verifiedBalances) { console.log(`Verified ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, verifiedBalances[property])}`) } } module.exports = { getZkSyncProvider, getEthereumProvider, depositToZkSync, registerAccount, displayZkSyncBalance, transfer, withdrawToEthereum, getFee, initAccount } ``` ## 更新 getFee 1. 修改 getFee 的參數 ethers 為 tokenSet 2. 改用 tokenSet.formatToken 把 fee.totalFee 從 BigNumber 轉為可讀形式 更新如下 ```javascript= 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 { // eslint-disable-next-line new-cap 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 } 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() } console.log(`Account ${wallet.address()} registered`) } async function depositToZkSync (zkSyncWallet, token, amountToDeposit, tokenSet) { const deposit = await zkSyncWallet.depositToSyncFromEthereum({ depositTo: zkSyncWallet.address(), token: token, amount: tokenSet.parseToken(token, amountToDeposit) }) try { await deposit.awaitReceipt() } catch (error) { console.log('Error while awaiting confirmation from the zkSync operators.') console.log(error) } } async function transfer (from, toAddress, amountToTransfer, transferFee, token, zksync, tokenSet) { const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(tokenSet.parseToken(token, amountToTransfer)) const closestPackableFee = zksync.utils.closestPackableTransactionFee(tokenSet.parseToken(token, 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) } // 1. On the next line, replace the last parameter (`ethers`) with `tokenSet` async function getFee(transactionType, address, token, zkSyncProvider, tokenSet) { const fee = await zkSyncProvider.getTransactionFee(transactionType, address, token) // 2. On the next line, use the `tokenSet` object instead of `ethers.utils.formatEther` return tokenSet.formatToken(token, fee.totalFee) } async function withdrawToEthereum (wallet, amountToWithdraw, withdrawalFee, token, zksync, tokenSet) { const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(tokenSet.parseToken(token, amountToWithdraw)) const closestPackableFee = zksync.utils.closestPackableTransactionFee(tokenSet.parseToken(token, withdrawalFee)) const withdraw = await wallet.withdrawFromSyncToEthereum({ ethAddress: wallet.address(), token: token, amount: closestPackableAmount, fee: closestPackableFee }) await withdraw.awaitVerifyReceipt() console.log('ZKP verification is complete') } async function displayZkSyncBalance (wallet, tokenSet) { const state = await wallet.getAccountState() const commitedBbalances = state.committed.balances const verifiedBalances = state.verified.balances for (const property in commitedBbalances) { console.log(`Commited ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, commitedBbalances[property])}`) } for (const property in verifiedBalances) { console.log(`Verified ${property} balance for ${wallet.address()}: ${tokenSet.formatToken(property, verifiedBalances[property])}`) } } module.exports = { getZkSyncProvider, getEthereumProvider, depositToZkSync, registerAccount, displayZkSyncBalance, transfer, withdrawToEthereum, getFee, initAccount } ```