---
# System prepended metadata

title: 從以太坊白皮書理解 web 3 概念 - Day22
tags: [' Web 3', it 鐵人賽 30 天, ' ethereum']

---

---
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
}

```