今天將會透過 Lession 17 - Intro to zkSync 來了解
何謂零知識證明與其應用
透過 zkSync 做到 off-chain 的 token 對換。
在密碼學,所謂的零知識證明是指一種驗證方法可以透過不直接讀取需要被驗證資料本身的資訊來驗證論述是否正確,不需要額外關於驗證資料本身的資訊。
所謂的零知識是指不需要關於關於被驗證資料的資訊。
舉例來說:
想象一下,您與一個蒙著眼睛的人共處一室。在您面前的桌子上有兩個球:白色和黑色。您需要在不透露顏色的情況下向第二個人(驗證者)證明這些球確實是不同的顏色。
透過一個方式,讓蒙著眼睛的人先把球藏在桌下,然後讓他隨機選出一個展示給您看。
然後再讓他已同樣的動作展示一次。這時您可以透漏球是否與上次是同一個。
為了避免協助驗證的人作弊,因此蒙著眼睛的人隨機選球。在做過多次驗證後,透過知道會有一顆球與另一顆不同。
蒙眼的本身不需要知道太多關於球顏色的內容,卻可以透過其他觀察者回答球是不是同一顆來驗證球顏色是不同的。
在這個範例裡驗證者是蒙眼的人,而驗證資料是球的顏色
zkSync 是一個實作零知識證明的框架,可用來加強不同鏈上的 token 交換的安全性,可以透過鏈下的運算來實作,也就是相對有效率。
使用者會透過傳輸 ERC20Token 到 zkSync Smart Contract
zkSync Smart Contrat 則會給予使用者等價的 Asset
zkSync 與 Ethereum 節點都是透過 JSON-RPC 格式欄做溝通
這邊將會使用 nodejs 套件來處理與節點溝通的細節
使用的套件有 ethers 與 zksync
建立一個資料夾然後透過以下指令初始化專案目錄
npm init -y
或是
yarn init -y
透過以下指令做套件安裝
npm i ethers zksync -S
或是
yarn add ethers zksync
zkSync 使用了 zk-SNARKs 標準代表 "Zero-Knowledge Succinct Non-interactive Arguments of Knowledge"
簡單來說,零知識證明是一種方法能夠讓 Alice(受驗證者)能夠讓不需要額外透露其他資訊就可以向 Bob(驗證者)證明 Alice 所知到的某些事實。
舉例來說: 使用 zk-SNARK, Bob 可以不查詢 Alice 帳號就可以知道 Alice 的帳戶內資產大於 0.5 ETH
zkSync 是透過一個發佈在 Ethereum 鏈上的 Smart Contrat 來持有所有資產,而大部分的運算都是使用鏈下處理。每次簽入一筆交易,這個協定就會送出一個對應的運算來處理多筆交易進入一個區塊,並且計算以下資料:
這些資料接著會送到一個 Smart Contract,這可以讓所有人都可以重建這些狀態。
SNARK 的驗證比單獨驗證每比交易還要便宜,並且會儲存狀態再鏈下也比鏈上便宜。
當要開始與 zkSync 節點互動時,需要透過 Provider 元件來處理。
Provider 如同 zkSync 節點與前端 app 的一個溝通橋梁。
建立兩個 javascript 應用 alice.js 與 bob.js
並且把共用的部份都放到 util.js
let zkSyncProvider;
try {
// Initialize the provider
} catch (error) {
// Display an error message
}
await zksync.getDefaultProvider(networkName);
console.log('Unable to connect to zkSync.')
console.log(error)
更新如下
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
}
如同其他錢包一樣,為了要能在鏈上做交易,必須建立一個帳號
帳號也等同於需要產生一把簽章密鑰
並且把簽章密鑰存放在錢包內
在做交易時,需要使用密鑰做簽章
這邊透過 zksync 與 ethers 函式庫整合了一個錢包物件 zksync.Wallet 可以來做上述的產生密鑰還有保管密鑰與做簽章的邏輯
為了要初始化 zksync.Wallet 必須要呼叫 zksync.Wallet.fromEthSigner
並且傳入兩個參數:
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
}
// Start here
async function initAccount(rinkebyWallet, zkSyncProvider, zksync) {
const zkSyncWallet = await zksync.Wallet.fromEthSigner(rinkebyWallet, zkSyncProvider)
return zkSyncWallet
}
前面提到可以使用 zkSync Wallet 來轉換資產
然而,要做簽章必須先檢查是否已經產生簽章密鑰
流程如下
if (!await wallet.isSigningKeySet()) {
// Your signing keys have not been set. You'll place the logic for setting it here.
}
if (await wallet.getAccountId() === undefined) {
throw new Error('Unknown account')
}
const changePubkey = await wallet.setSigningKey()
await changePubkey.awaitReceipt()
宣告一個 registerAccount function
更新如下
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`)
// Start here
if (!await wallet.isSigningKeySet()) {
if (await wallet.getAccountId() === undefined) {
throw new Error('Unknown account')
}
const changePubkey = await wallet.setSigningKey()
await changePubkey.awaitReceipt()
}
}
使用者可以對 zkSync 執行以下類型操作
具有優先順序的操作: 這類操作使用者會發起在 Ethereum 上。舉例來說:把資產存入
交易: 這類型操作使用者會發起來 zkSync 上。舉例來說: 建立一個付款單
zkSync 的交易具有以下特性:
要把 assets 存入 zkSync 只需要呼叫 depositToSyncFromEthereum 如下
onst deposit = await zkSyncWallet.depositToSyncFromEthereum({
depositTo: recipient,
token: token,
amount: amountInWei
})
在呼叫之後,交易會被 Ethereum 上的礦工節點處理。
在這個階段,交易還不會顯示在 zkSync。
而這時通常會透過 awaitEthereumTxCommit 來等待交易處理結果如下
await deposit.awaitEthereumTxCommit()
然後,交易完成後就會推送到 zkSync 。
當這個發生時, 用戶的 balance 就會被更新
為了確認結果可以使用 awaitReceipt 處理如下
await deposit.awaitVerifyReceipt()
try {
await deposit.awaitReceipt()
} catch (error) {
}
console.log('Error while awaiting confirmation from the zkSync operators.')
console.log(error)
更新如下
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`)
}
// Start here
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)
}
}
要在 zkSync 轉移 assets 需要以下兩個步驟:
const transfer = await wallet.syncTransfer({
to: toAddress,
token: token,
amount: closestPackableAmount,
fee: closestPackableFee
})
const transferReceipt = await transfer.awaitReceipt()
特別要注意的是 zkSync 做 Tranfer 的精準度是有限的
其中,轉移量的精準度是 5-byte long floating-point
而 fee 的精準度是 2-byte long floating-point
需要使用以下方式做精準度轉換
const closestPackableAmount = zksync.utils.closestPackableTransactionAmount(amountToTransferInWei)
const closestPackableFee = zksync.utils.closestPackableTransactionFee(transferFeeInWei)
console.log('Got transfer receipt.')
console.log(transferReceipt)
更新如下:
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, 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)
}
}
async function transfer (from, toAddress, amountToTransfer, transferFee, token, zksync, ethers) {
// Start here
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)
}
在 zkSync , 運算需要費用雖然便宜
一共有兩種 fee:
以下計算 fee 的範例
const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token)
getTransactionFee 需要 3 個參數
回傳值的型別會如下
export interface Fee {
// Operation type (amount of chunks in operation differs and impacts the total fee).
feeType: "Withdraw" | "Transfer" | "TransferToNew",
// Amount of gas used by transaction
gasTxAmount: utils.BigNumber,
// Gas price (in wei)
gasPriceWei: utils.BigNumber,
// Ethereum gas part of fee (in wei)
gasFee: utils.BigNumber,
// Zero-knowledge proof part of fee (in wei)
zkpFee: utils.BigNumber,
// Total fee amount (in wei)
// This value represents the summarized fee components, and it should be used as a fee
// for the actual operation.
totalFee: utils.BigNumber,
}
可以發現回傳值會是 BigNumber
如果要轉回可讀的格式就必須使用以下範例
const fee = ethers.utils.formatEther(feeInWei.toString())
更新如下:
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, 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)
}
}
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)
}
// Start here
async function getFee (transactionType, address, token, zkSyncProvider, ethers) {
const feeInWei = await zkSyncProvider.getTransactionFee(transactionType, address, token)
return ethers.utils.formatEther(feeInWei.totalFee.toString())
}
要從 zkSync 把 asset 提出到 Ethereum 步驟如下:
const withdraw = await wallet.withdrawFromSyncToEthereum({
ethAddress: recipientEthereumAddress,
token: token,
amount: closestPackableAmount,
fee: closestPackableFee
})
特別要注意以下幾點:
更新如下
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, 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)
}
}
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) {
// Start here
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')
}
當使用者在 zkSync 轉移 asset , 曠工節點會把這個交易加入下一個區塊並且新增這新區塊到 Ethereum 上的 zkSync Smart Contract 透過一個 Commit 交易。
接著, SNARK 在驗證完 block 內的每個交易後會把 Verify 交易放到 Ethereum 上。一旦 Verify 交易被曠工寫入 Ethereum ,整個轉移流程就完成了
在以上過程中,基本上會把 balance 分為兩種類型
可以透過以下語法取得 balance
const committedETHBalance = await wallet.getBalance('ETH')
const verifiedETHBalance = await wallet.getBalance('ETH', 'verified')
const state = await wallet.getAccountState()
state 物件內容格式如下
{ address: '0xc26f2adeeebbad73f25329ffa12cd3889429b5b6',
committed:
{ balances: { ETH: '100000000000000000' },
nonce: 1,
pubKeyHash: 'sync:de9de11bdad08aa1cdc2beb5b2b7c7f29c10f079' },
depositing: { balances: {} },
id: 138,
verified:
{ balances: { ETH: '100000000000000000' },
nonce: 1,
pubKeyHash: 'sync:de9de11bdad08aa1cdc2beb5b2b7c7f29c10f079' } }
特別要注意的是當 account balance 的值是 0, 則 state.committed.balances.ETH 會是 undefined
特別要小心處理這塊邏輯
console.log(`Commited ETH balance for ${wallet.address()}: ${ethers.utils.formatEther(state.committed.balances.ETH)}`)
console.log(`Commited ETH balance for ${wallet.address()}: 0`)
console.log(`Verified ETH balance for ${wallet.address()}: 0`)
更新如下
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, 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)
}
}
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')
}
// Start here
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`)
}
}
接下來實作與 zkSync 互動的功能
這邊使用兩個 nodejs 的應用個別代表 shopkeeper(Bob) 以及 customer(Alice)
(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)
})()
為了要讓 bob 可以簽章
這裡需要建立 Rinkeby Wallet
建立語法如下
const rinkebyWallet = new ethers.Wallet(process.env.YOUR_PRIVATE_KEY, ethersProvider)
ethers.Wallet 提供以下語法可以讓使用者察看 address 與 balance
console.log(`Bob's Rinkeby address is: ${rinkebyWallet.address}`)
console.log(`Bob's initial balance on Rinkeby is: ${ethers.utils.formatEther(await rinkebyWallet.getBalance())}`)
更新如下
(async () => {
const ethers = require('ethers')
const zksync = require('zksync')
const utils = require('./utils')
const zkSyncProvider = await utils.getZkSyncProvider(zksync, process.env.NETWORK_NAME)
const ethersProvider = await utils.getEthereumProvider(ethers, process.env.NETWORK_NAME)
// Start here
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)
})()
當轉移 asset 發生後,則需要更新 balance
因為 balance 需要等 blockchain 同步完 transaction 才能更新
因此需要透過 setInterval 來不斷監聽變化
且為了避免應用關閉時, setInterval 沒有釋放掉
所以需要使用 process.on('SIGINT') 語法來監聽 app 關閉來做釋放資源的邏輯
更新如下
(async () => {
const ethers = require('ethers')
const zksync = require('zksync')
const utils = require('./utils')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 5000
const zkSyncProvider = await utils.getZkSyncProvider(zksync, process.env.NETWORK_NAME)
const ethersProvider = await utils.getEthereumProvider(ethers, process.env.NETWORK_NAME)
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)
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)
})()
接下來需要實作與 bob.js 互動的 alice.js
實作流程如下
const token = 'ETH'
const amountToDeposit = '0.05'
const amountToTransfer = '0.02'
const amountToWithdraw = '0.002'
以下是 alice 得操作流程
更新如下:
(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)
})()
更新如下:
(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')
await utils.depositToZkSync(aliceZkSyncWallet, token, amountToDeposit, ethers)
await utils.displayZkSyncBalance(aliceZkSyncWallet, ethers)
await utils.registerAccount(aliceZkSyncWallet)
console.log('Transferring')
// Start here
const transferFee = await utils.getFee('Transfer', aliceRinkebyWallet.address, token, zkSyncProvider, ethers)
await utils.transfer(aliceZkSyncWallet, process.env.BOB_ADDRESS, amountToTransfer, transferFee, token, zksync, ethers)
})()
更新如下
(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')
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')
// Start here
const withdrawalFee = await utils.getFee('Withdraw', aliceRinkebyWallet.address, token, zkSyncProvider, ethers)
await utils.withdrawToEthereum(aliceZkSyncWallet, amountToWithdraw, withdrawalFee, token, zksync, ethers)
})()
https://www.blocktempo.com/zero-knowledge-proof-zkp-chagelly-column/