Part 2: Interacting with Vote Contract Using TezosSwift and Combine
Following up on the first part of this tutorial, in this tutorial we shall see how we can leverage tezosgen to easily interact with our contract in our SwiftUI/Combine codebase.
Generating our Contract
Once you deploy the contract using smartpy IDE at the bottom you should see “Contract data” and a tab “Code json”. From there we will need the contents of `storage` and `parameter` as shown in the image below:
You can find the final JSON file here. To follow the rest of tutorial, I’d advise you to download the source code here. Let’s see how we can generate a Swift code for our contract. After downloading tezosgen, we can run `tezosgen generate RateContract contract.json -x RateMyTeam.xcodeproj -o RateMyTeam/Generated --extensions combine -t RateMyTeam`. What this will essentially do is translate `contract.json` to `RateContract.swift` with the added extensions for using Combine. Similarly to the previous tutorial, I will go through how to implement voting and ending functionality but feel free to roam through the rest of the codebase!
Vote Method
The interaction with our contract is handled in `RateRepository.swift`
For voting we will need three additional properties (apart from the vote function itself):
- `tezosClient` for interacting with a given Tezos network node (for more about it see TezosSwift documentation)
- `userRepository`: holds underlying `Wallet` (also from TezosSwift)
- `@Published var state: RateRepositoryState`: Tracks currently available info about contracts in our app. It is annotated with `@Published` because we want to trigger changes for views that are bound to `RateRepository`.
This is what the final vote function looks like:
private func vote(votes: [Candidate.ID: Int], contractAddress: String) {
#1
guard let wallet = userRepository.state.value.wallet else { return }
let nonZeroVotes = votes.filter { $0.value != 0 }
#2
tezosClient
.rateContract(at: contractAddress)
.vote(nonZeroVotes)
.callPublisher(from: wallet, amount: Tez(0))
.handleEvents(receiveOutput: { [weak self] output in
print(output)
self?.addVotes(votes, for: contractAddress)
}, receiveCompletion: { completion in
print(completion)
})
.startAndStore(in: &cancellables)
}
This function accepts a dictionary of `votes` - key being id of a candidate (`address`), `contractAddress` is, surprisingly, the address of the contract we want to interact with.
In the #1 part we first unwrap our wallet to be able to sign a later transaction. `nonZeroVotes` is just a convenient variable to ensure that we do not cast any votes with value of zero.
#2 is the most important part of our code. `tezosClient.rateContract(at: contractAddress)` returns an invocable object of the previously generated `RateContract`. On that invocable object (`ContractBox` in the generated code) we can call `vote` function with our `nonZeroVotes`. Finally, to send operation to the contract we run `callPublisher(from: wallet, amount: Tez(0))` - that is send a new transaction from our wallet with zero amount of tez (fees will be automatically calculated by TezosSwift).
`callPublisher` returns a `Publisher` where output value is a signature of the transaction. If we do not receive that signature, it means the call has failed. If all succeeds, we add the votes to our `rateRepositoryState` in a helper function `addVotes` (this way the new changes will get propagated to all our views). And that’s it for our `vote` function!
End Method
Our end method is very similar to a voting one (but even a little bit simpler!):
private func endVote(_ contractAddress: String) {
guard let wallet = userRepository.state.value.wallet else { return }
tezosClient
.rateContract(at: contractAddress)
#1
.end()
.callPublisher(from: wallet, amount: Tez(0))
.handleEvents(receiveOutput: { [weak self] output in
print(output)
#2
self?.endVote(of: contractAddress)
}, receiveCompletion: { completion in
print(completion)
})
.startAndStore(in: &cancellables)
}
The only difference being at #1 where we call `.end()`. Then in #2 we will again call a helper function that locally updates our `rateRepository`.
Updating Contracts
So far, we have only updated the data locally – but what about syncing them with the current state on the blockchain? That becomes quite simple, too:
private func updateStore(of contract: String) {
tezosClient.rateContract(at: contract)
.statusPublisher()
.map(\.storage)
// Do something with the synced storage
}
We leverage `statusPublisher()` method that returns `RateContractStorage` that holds the current contract’s storage. Easy enough!
Final Notes
I hope this tutorial has given you enough information, so you can start working on your own Tezos smart contract iOS app. The generated code from tezosgen should make it really easy to start calling your smart contract and even incorporate into your iOS app (be it in SwiftUI, or not!). If you have any questions regarding app development using Tezos blockchain feel free to ask us at Ackee.