zkSync2-swift 0.0.2

zkSync2-swift 0.0.2

Bojan 维护。



 
依赖项
Alamofire~> 5.0
web3swift-zksync2>= 2.6.5-zksync2
 

  • Matter Labs 团队

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)

示例

所有示例的完整代码可在此处找到。示例已配置为与 zkSyncGoerli 测试网络交互。

检查余额

这是一个在 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()