zksync2-swift
先决条件
- IOS: >=13.0
- MacOS: >=11.0
CocoaPods 集成
要使用 CocoaPods 安装 zkSync,请在 Podfile 中添加 zkSync2 已知的 pod
pod 'zkSync2-swift'
Swift 包管理器集成
要使用 Swift 包管理器安装 zkSync,将 zkSync2 添加到包依赖项中
'github.com/zksync-sdk/zksync2-swift'
连接到 ZkSync
集成 zkSync2 依赖项后,使用操作节点端点连接到 zkSync。
let zkSync: ZkSync = ZkSyncImpl(URL(string: "https://testnet.era.zksync.dev")!)
连接到 Ethereum
同样,使用 eth_goerli 节点的端点连接到 Ethereum。
let ethereum: web3 = try! Web3.new(URL(string: "https://rpc.ankr.com/eth_goerli")!)
let credentials = Credentials(<WALLET_PRIVATE_KEY>)
let keystoreManager = KeystoreManager([credentials])
ethereum.addKeystoreManager(keystoreManager)
有用的属性和函数
这是在执行某些 ZkSync 函数时需要使用的计算属性列表。
var chainId: BigUInt {
try! zkSync.web3.eth.getChainIdPromise().wait()
}
var nonce: BigUInt {
try! zkSync.web3.eth.getTransactionCountPromise(
address: EthereumAddress(signer.address)!,
onBlock: ZkBlockParameterName.committed.rawValue
).wait()
}
func signTransaction(_ transaction: inout EthereumTransaction) {
let signature = signer.signTypedData(signer.domain, typedData: transaction).addHexPrefix()
let unmarshalledSignature = SECP256K1.unmarshalSignature(signatureData: Data(fromHex: signature)!)!
transaction.envelope.r = BigUInt(fromHex: unmarshalledSignature.r.toHexString().addHexPrefix())!
transaction.envelope.s = BigUInt(fromHex: unmarshalledSignature.s.toHexString().addHexPrefix())!
transaction.envelope.v = BigUInt(unmarshalledSignature.v)
}
创建 ZkSync 钱包
要在 zkSync 中控制您的账户,请使用 ZkSyncWallet
对象。它可以签名交易并将它们发送到 zkSync 网络。
let credentials = Credentials(<WALLET_PRIVATE_KEY>)
let signer = PrivateKeyEthSigner(credentials, chainId: chainId)
let wallet = ZkSyncWallet(zkSync, ethereum: ethereum, ethSigner: signer, feeToken: Token.ETH)
示例
所有示例的完整代码可在此处找到。示例已配置为与 zkSync
和 Goerli
测试网络交互。
检查余额
这是一个在 zkSync 上检查余额的示例。
let balance = try! wallet.getBalance().wait()
或者
zkSync.zksGetAllAccountBalances(signer.address) { result in
}
获取 zkSync 上的所有已确认令牌
这是一个获取 zkSync 上所有已确认令牌的示例。
zkSync.zksGetConfirmedTokens(0, limit: 255) { result in
}
存款 ETH
这是一个从以太坊网络(L1)向 zkSync 网络(L2)存款 ETH 的示例。
zkSync.zksMainContract { result in
DispatchQueue.global().async {
switch result {
case .success(let address):
let zkSyncContract = self.ethereum.contract(
Web3.Utils.IZkSync,
at: EthereumAddress(address)
)!
let l1ERC20Bridge = self.zkSync.web3.contract(
Web3.Utils.IL1Bridge,
at: EthereumAddress(self.signer.address)
)!
let defaultEthereumProvider = DefaultEthereumProvider(
self.ethereum,
l1ERC20Bridge: l1ERC20Bridge,
zkSyncContract: zkSyncContract,
gasProvider: DefaultGasProvider()
)
let amount = BigUInt(1_000_000_000_000)
_ = try! defaultEthereumProvider.deposit(
with: Token.ETH,
amount: amount,
operatorTips: BigUInt(0),
to: self.signer.address
).wait()
case .failure(let error):
throw error
}
}
}
或者
let amount = BigUInt(1_000_000_000_000)
_ = try! wallet.deposit(
signer.address,
amount: amount
).wait()
转账ETH
这是一个如何在zkSync网络中转账ETH的示例。
let value = BigUInt(1_000_000_000_000)
var estimate = EthereumTransaction.createFunctionCallTransaction(
from: EthereumAddress(signer.address)!,
to: EthereumAddress(<TO_ADDRESS>)!,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
data: Data()
)
let fee = try! zkSync.zksEstimateFee(estimate).wait()
estimate.parameters.EIP712Meta?.gasPerPubdata = fee.gasPerPubdataLimit
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.type = .eip712
transactionOptions.from = EthereumAddress(signer.address)!
transactionOptions.to = estimate.to
transactionOptions.gasLimit = .manual(fee.gasLimit)
transactionOptions.maxPriorityFeePerGas = .manual(fee.maxPriorityFeePerGas)
transactionOptions.maxFeePerGas = .manual(fee.maxFeePerGas)
transactionOptions.value = value
transactionOptions.nonce = .manual(nonce)
transactionOptions.chainID = chainId
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
value: value,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
let result = try! zkSync.web3.eth.sendRawTransactionPromise(transaction).wait()
let receipt = transactionReceiptProcessor.waitForTransactionReceipt(hash: result.hash)
assert(receipt?.status == .ok)
或者
let amount = BigUInt(1_000_000_000_000)
_ = try! wallet.transfer(
<TO_ADDRESS>,
amount: amount
).wait()
提现ETH
这是从zkSync网络(L2)提取ETH到Ethereum网络(L1)的示例。
let contract = zkSync.web3.contract(Web3.Utils.IEthToken)!
let value = BigUInt(1_000_000_000_000)
let inputs = [
ABI.Element.InOut(name: "_l1Receiver", type: .address)
]
let function = ABI.Element.Function(
name: "withdraw",
inputs: inputs,
outputs: [],
constant: false,
payable: true
)
let withdrawFunction: ABI.Element = .function(function)
let parameters: [AnyObject] = [
EthereumAddress(signer.address)! as AnyObject,
]
let calldata = withdrawFunction.encodeParameters(parameters)!
var estimate = EthereumTransaction.createFunctionCallTransaction(
from: EthereumAddress(signer.address)!,
to: EthereumAddress.L2EthTokenAddress,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
value: value,
data: calldata
)
estimate.envelope.parameters.chainID = signer.domain.chainId
let fee = try! zkSync.zksEstimateFee(estimate).wait()
estimate.parameters.EIP712Meta?.gasPerPubdata = BigUInt(160000)
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.type = .eip712
transactionOptions.from = EthereumAddress(signer.address)!
transactionOptions.to = estimate.to
transactionOptions.maxPriorityFeePerGas = .manual(fee.maxPriorityFeePerGas)
transactionOptions.maxFeePerGas = .manual(fee.maxFeePerGas)
transactionOptions.value = value
transactionOptions.nonce = .manual(nonce)
transactionOptions.chainID = chainId
let estimateGas = try! self.zkSync.web3.eth.estimateGas(estimate, transactionOptions: transactionOptions)
transactionOptions.gasLimit = .manual(estimateGas)
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
let result = try! contract.web3.eth.sendRawTransactionPromise(transaction).wait()
let txHash = result.hash
let index = 0
guard let receipt = transactionReceiptProcessor.waitForTransactionReceipt(hash: txHash) else {
fatalError("Transaction failed.")
}
assert(receipt.status == .ok)
let l1ERC20Bridge = zkSync.web3.contract(
Web3.Utils.IL1Bridge,
at: EthereumAddress(signer.address)
)!
zkSync.zksMainContract { result in
DispatchQueue.global().async {
switch result {
case .success(let address):
let zkSyncContract = self.ethereum.contract(
Web3.Utils.IZkSync,
at: EthereumAddress(address)
)!
let defaultEthereumProvider = DefaultEthereumProvider(self.ethereum, l1ERC20Bridge: l1ERC20Bridge, zkSyncContract: zkSyncContract, gasProvider: DefaultGasProvider())
let topic = "L1MessageSent(address,bytes32,bytes)"
let log = receipt.logs.filter({
if $0.address.address == ZkSyncAddresses.MessengerAddress && $0.topics.first == EIP712.keccak256(topic) {
return true
}
return false
})[index]
let l2tol1log = receipt.l2ToL1Logs!.filter({
if $0.sender.address == ZkSyncAddresses.MessengerAddress {
return true
}
return false
})[index]
self.zkSync.zksGetL2ToL1LogProof(txHash, logIndex: Int(l2tol1log.logIndex)) { result in
DispatchQueue.global().async {
switch result {
case .success(let proof):
let contract = self.zkSync.web3.contract(Web3.Utils.IL1Messenger)!
let eventData = contract.parseEvent(log).eventData
let message = eventData?["_message"] as? Data ?? Data()
_ = try! defaultEthereumProvider.finalizeEthWithdrawal(
receipt.l1BatchNumber,
l2MessageIndex: BigUInt(proof.id),
l2TxNumberInBlock: receipt.l1BatchTxIndex,
message: message,
proof: proof.proof.compactMap({ Data(fromHex: $0) }),
nonce: self.nonce
).wait()
case .failure(let error):
throw error
}
}
}
case .failure(let error):
throw error
}
}
}
或者
let amount = BigUInt(1_000_000_000_000)
_ = try! wallet.withdraw(
signer.address,
amount: amount,
token: Token.ETH
).wait()
铸造自定义代币
let contract = zkSync.web3.contract(Web3.Utils.IToken, at: EthereumAddress(<SMART_CONTRACT_ADDRESS>)!)!
let value = BigUInt(100)
let parameters = [
EthereumAddress(signer.address)! as AnyObject,
value as AnyObject
] as [AnyObject]
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.from = EthereumAddress(signer.address)!
guard let writeTransaction = contract.write(
"mint",
parameters: parameters,
transactionOptions: transactionOptions
) else {
return
}
var estimate = EthereumTransaction.createFunctionCallTransaction(
from: EthereumAddress(signer.address)!,
to: writeTransaction.transaction.to,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
data: writeTransaction.transaction.data
)
let fee = try! zkSync.zksEstimateFee(estimate).wait()
estimate.parameters.EIP712Meta?.gasPerPubdata = BigUInt(160000)
transactionOptions = TransactionOptions.defaultOptions
transactionOptions.type = .eip712
transactionOptions.from = EthereumAddress(signer.address)!
transactionOptions.to = estimate.to
transactionOptions.maxPriorityFeePerGas = .manual(fee.maxPriorityFeePerGas)
transactionOptions.maxFeePerGas = .manual(fee.maxFeePerGas)
transactionOptions.nonce = .manual(nonce)
transactionOptions.chainID = chainId
let estimateGas = try! self.zkSync.web3.eth.estimateGas(estimate, transactionOptions: transactionOptions)
transactionOptions.gasLimit = .manual(estimateGas)
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
_ = try! zkSync.web3.eth.sendRawTransactionPromise(transaction).wait()
智能合约和智能账户部署
使用zkSync,您可以通过将合约构建为二进制格式并将其部署到zkSync网络,使用create和create2操作码来部署合约。
- Storage:无构造函数的合约。
- Incrementer:包含构造函数的合约。
- Demo:依赖于Foo合约的合约。
- Token:自定义的ERC20代币。
- Paymaster:自定义的支付主,使用ERC20代币支付交易费。
有关如何使用 zksolc
编译器编译 Solidity 智能合约的用户指南。该编译器生成一个 *.zbin
文件和一个 combined.json
文件,其中包含智能合约的字节码和 ABI。该 combined.json
文件被 abigen
工具用于生成智能合约绑定。以下示例中使用这些文件。
在某些情况下,在部署合约之前,您可能需要获取合约地址。使用以下方法:
utils.ComputeL2CreateAddress()
获取使用 create 指令部署时的预计算智能合约地址。utils.ComputeL2Create2Address()
获取使用 create2 指令部署时的预计算智能合约地址。
使用 create2 指令部署智能合约
let bytecodeData = <SMART_CONTRACT_BYTECODE>
let contractTransaction = EthereumTransaction.create2ContractTransaction(
from: EthereumAddress(signer.address)!,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
bytecode: bytecodeData,
deps: [bytecodeData],
calldata: Data(),
salt: Data(),
chainId: signer.domain.chainId
)
let precomputedAddress = ContractDeployer.computeL2Create2Address(
EthereumAddress(signer.address)!,
bytecode: bytecodeData,
constructor: Data(),
salt: Data()
)
let chainID = signer.domain.chainId
var estimate = EthereumTransaction.createFunctionCallTransaction(
from: EthereumAddress(signer.address)!,
to: contractTransaction.to,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
data: contractTransaction.data
)
estimate.parameters.EIP712Meta?.factoryDeps = [bytecodeData]
let fee = try! zkSync.zksEstimateFee(estimate).wait()
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.type = .eip712
transactionOptions.chainID = chainID
transactionOptions.nonce = .manual(nonce)
transactionOptions.to = contractTransaction.to
transactionOptions.value = contractTransaction.value
transactionOptions.gasLimit = .manual(fee.gasLimit)
transactionOptions.maxPriorityFeePerGas = .manual(fee.maxPriorityFeePerGas)
transactionOptions.maxFeePerGas = .manual(fee.maxFeePerGas)
transactionOptions.from = contractTransaction.parameters.from
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
ethereumParameters.EIP712Meta?.factoryDeps = [bytecodeData]
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
guard let message = transaction.encode(for: .transaction) else {
fatalError("Failed to encode transaction.")
}
let result = try! zkSync.web3.eth.sendRawTransactionPromise(message).wait()
let receipt = transactionReceiptProcessor.waitForTransactionReceipt(hash: result.hash)
assert(receipt?.status == .ok)
assert(precomputedAddress == receipt?.contractAddress)
使用 create2 指令部署智能合约
let bytecodeData = <SMART_ACCOUNT_BYTECODE>
let inputs = [
ABI.Element.InOut(name: "erc20", type: .address),
]
let function = ABI.Element.Function(
name: "",
inputs: inputs,
outputs: [],
constant: false,
payable: false)
let elementFunction: ABI.Element = .function(function)
let parameters: [AnyObject] = [
EthereumAddress(<TOKEN_ADDRESS>)! as AnyObject
]
guard var encodedCallData = elementFunction.encodeParameters(parameters) else {
fatalError("Failed to encode function.")
}
// Removing signature prefix, which is first 4 bytes
for _ in 0..<4 {
encodedCallData = encodedCallData.dropFirst()
}
let estimate = EthereumTransaction.create2AccountTransaction(
from: EthereumAddress(signer.address)!,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
bytecode: bytecodeData,
deps: [bytecodeData],
calldata: encodedCallData,
salt: Data(),
chainId: signer.domain.chainId
)
let chainID = signer.domain.chainId
let gasPrice = try! zkSync.web3.eth.getGasPrice()
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.gasPrice = .manual(BigUInt.zero)
transactionOptions.type = .eip712
transactionOptions.chainID = chainID
transactionOptions.nonce = .manual(nonce)
transactionOptions.to = estimate.to
transactionOptions.value = BigUInt.zero
transactionOptions.maxPriorityFeePerGas = .manual(BigUInt(100000000))
transactionOptions.maxFeePerGas = .manual(gasPrice)
transactionOptions.from = estimate.parameters.from
let estimateGas = try! zkSync.web3.eth.estimateGas(estimate, transactionOptions: transactionOptions)
transactionOptions.gasLimit = .manual(estimateGas)
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
ethereumParameters.EIP712Meta?.factoryDeps = [bytecodeData]
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
guard let message = transaction.encode(for: .transaction) else {
fatalError("Failed to encode transaction.")
}
let result = try! zkSync.web3.eth.sendRawTransactionPromise(message).wait()
let receipt = transactionReceiptProcessor.waitForTransactionReceipt(hash: result.hash)
assert(receipt?.status == .ok)
使用 paymaster
此示例演示了如何使用 paymaster 来便于使用 ERC20 令牌支付费用。用户发起一个配置为通过 paymaster 使用 ERC20 令牌进行支付的分币交易。在交易执行过程中,paymaster 从用户处接收 ERC20 令牌,并使用 ETH 支付交易费。
let contract = zkSync.web3.contract(Web3.Utils.IToken, at: EthereumAddress(<SMART_CONTRACT_ADDRESS>)!)!
let value = BigUInt(1_000)
let parameters = [
EthereumAddress(signer.address)! as AnyObject,
value as AnyObject
] as [AnyObject]
var transactionOptions = TransactionOptions.defaultOptions
transactionOptions.from = EthereumAddress(signer.address)!
guard let writeTransaction = contract.write(
"mint",
parameters: parameters,
transactionOptions: transactionOptions
) else {
return
}
var estimate = EthereumTransaction.createFunctionCallTransaction(
from: EthereumAddress(signer.address)!,
to: writeTransaction.transaction.to,
gasPrice: BigUInt.zero,
gasLimit: BigUInt.zero,
data: writeTransaction.transaction.data
)
let fee = try! zkSync.zksEstimateFee(estimate).wait()
estimate.parameters.EIP712Meta?.gasPerPubdata = BigUInt(160000)
let paymasterAddress = EthereumAddress(<PAYMASTER_ADDRESS>)!
let paymasterInput = Paymaster.encodeApprovalBased(
EthereumAddress(<TO_ADDRESS>)!,
minimalAllowance: BigUInt(1),
paymasterInput: Data()
)
estimate.parameters.EIP712Meta?.paymasterParams = PaymasterParams(paymaster: paymasterAddress, paymasterInput: paymasterInput)
transactionOptions = TransactionOptions.defaultOptions
transactionOptions.type = .eip712
transactionOptions.from = EthereumAddress(signer.address)!
transactionOptions.to = estimate.to
transactionOptions.maxPriorityFeePerGas = .manual(fee.maxPriorityFeePerGas)
transactionOptions.maxFeePerGas = .manual(fee.maxFeePerGas)
transactionOptions.nonce = .manual(nonce)
transactionOptions.chainID = chainId
let estimateGas = try! self.zkSync.web3.eth.estimateGas(estimate, transactionOptions: transactionOptions)
transactionOptions.gasLimit = .manual(estimateGas)
var ethereumParameters = EthereumParameters(from: transactionOptions)
ethereumParameters.EIP712Meta = estimate.parameters.EIP712Meta
var transaction = EthereumTransaction(
type: .eip712,
to: estimate.to,
nonce: nonce,
chainID: chainId,
data: estimate.data,
parameters: ethereumParameters
)
signTransaction(&transaction)
_ = try! zkSync.web3.eth.sendRawTransactionPromise(transaction).wait()