How to interact with Smart Contract deployed on Blockchain from Swift app - swift

It is my first time working with smart contracts and my goal is to create a mobile app which can interact with it by calling methods to save or retrieve data. For this reason I have created a very simple contract using Remix and I have also deployed on Rinkeby testnet.
contract Storage {
uint256 number;
function store(uint256 num) public {
number = num;
}
function retrieve() public view returns (uint256){
return number;
}
}
Then I built a SwiftUI app, which has one button. When I press this button, I want to call the store method and save some int number. For example number 9. Therefore I have created a function called write which looks as following:
let myInt = 9
func write() {
let web3 = Web3.InfuraRinkebyWeb3(accessToken: "https://rinkeby.infura.io/v3/a146daf63d93490995823f0910f50118")
let walletAddress = EthereumAddress("0xc65943Fae5a554e7DCF916F8828a73E5f7b1bDCd")! // Your wallet address
let contractMethod = "store" // Contract method you want to write
let contractABI = contractABIString // Contract ABI
let contractAddress = EthereumAddress("0x2826C42354FE5B816c7E21AD9e3B395Ced512C0C")!
let abiVersion = 2 // Contract ABI version
let parameters = [myInt] as [AnyObject]
let extraData: Data = Data() // Extra data for contract method
let contract = web3.contract(contractABI, at: contractAddress, abiVersion: abiVersion)!
var options = TransactionOptions.defaultOptions
options.from = walletAddress
options.gasPrice = .automatic
options.gasLimit = .automatic
do {
contract.write(
contractMethod,
parameters: parameters,
extraData: extraData,
transactionOptions: options)
} catch {
print("error:", error)
}
Unfortunately, when I run this code, nothing is happening. I don't get any error but when I refresh the contract I see that number 9 is not passed.
I am using web3swift library, https://github.com/skywinder/web3swift/tree/master#send-erc-20-token. According to the documentation, it should be fine, but it is not working, therefore I would really appreciate some assistant to make it work or some example projects where I can take a look, because I also couldn't find anything.
I found some other project using JS and I see the people there use their private keys, maybe I also need it, but since it is not shown in the documentation, I was not sure how to use it.

The smart contract interaction requires a few initialisations steps:
Your wallet
KeyStoreManager
web3
Let's assume you already got a Metamask wallet.
Separately you need to have a separate file containing the ABI of the smartContract you want to interact with.
It is an array and having it in a Swift.String is easier.(I won't show you this step)
struct Wallet {
let address: String
let data: Data
let name: String
let isHD: Bool
}
class SmartContractInteraction {
var wallet: Wallet!
var keystoreManager: KeystoreManager!
var web3: web3!
init() {
wallet = initializeWallet()
keystoreManager = getKeyStoreManager()
web3 = initializeweb3()
}
private func initializeWallet() -> Wallet? {
let password = "PasswordMetamask"
let key = "AccountPrivateKey"
let formattedKey = key.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey)!
let name = "Account 1"
do {
let keystore = try EthereumKeystoreV3(privateKey: dataKey, password: password)!
let keyData = try JSONEncoder().encode(keystore.keystoreParams)
let address = keystore.addresses!.first!.address
return Wallet(address: address, data: keyData, name: name, isHD: false)
} catch {
print("wallet init failed: \(error)")
return nil
}
}
private func getKeyStoreManager() -> KeystoreManager {
let data = wallet.data
let keystoreManager: KeystoreManager
if wallet.isHD {
let keystore = BIP32Keystore(data)!
keystoreManager = KeystoreManager([keystore])
} else {
let keystore = EthereumKeystoreV3(data)!
keystoreManager = KeystoreManager([keystore])
}
return keystoreManager
}
private func initializeweb3() -> web3 {
let endpoint = "https://ropsten.infura.io/v3/....."
let web3 = web3swift.web3(provider: Web3HttpProvider(URL(string: endpoint)!)!)
web3.addKeystoreManager(keystoreManager)
return web3
}
func callSmartContract() {
let value: String = "1"
let walletAddress = EthereumAddress(wallet.address)!
let contractAddress = EthereumAddress("SmartContractAddress")!
let contractMethod = "store"
let contractABI = MyContractABI
let abiVersion = 2
let parameters = [9] as [AnyObject]
let extraData: Data = Data()
let contract = web3.contract(contractABI, at: contractAddress, abiVersion: abiVersion)!
let amount = Web3.Utils.parseToBigUInt(value, units: .wei)
var options = TransactionOptions.defaultOptions
options.value = amount
options.from = walletAddress
options.gasPrice = .automatic
options.gasLimit = .automatic
let tx = contract.write(
contractMethod,
parameters: parameters,
extraData: extraData,
transactionOptions: options)!
do {
let password = "MetamaskPassword"
let result = try tx.send(password: password)
print(result)
} catch {
print("Token Balance failed: \(error)")
}
}
}
Like this should work, I think passing the value/option.value in the smartContract call method isn't necessary, but since I don't have your ABI I prefer to not remove anything from how it worked for me. Also I am not sure about the type of the number you pass.
Feel free to edit if this works without :)

Related

How to conduct an ETH transfer from web3swift?

How do I send GoerliETH from one wallet to considering I have my Wallet Address, Private Key, Infura Goerli API Key and the recipient Wallet Address.
I have tried to get balanceOf for the wallet so far, and that works with the following code I found elsewhere :
import web3swift
import BigInt
struct Wallet {
let address: String
let data: Data
let name: String
let isHD: Bool
}
struct HDKey {
let name: String?
let address: String
}
let password = "SOME_PASSWORD"
let key = "MY_PRIVATE_KEY" // Some private key
let formattedKey = key.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey)!
let keystore = try! EthereumKeystoreV3(privateKey: dataKey, password: password)!
let name = "MY_WALLET_NAME"
let keyData = try! JSONEncoder().encode(keystore.keystoreParams)
let address = keystore.addresses!.first!.address
let wallet = Wallet(address: address, data: keyData, name: name, isHD: false)
let data = wallet.data
let keystoreManager: KeystoreManager
if wallet.isHD {
let keystore = BIP32Keystore(data)!
keystoreManager = KeystoreManager([keystore])
} else {
let keystore = EthereumKeystoreV3(data)!
keystoreManager = KeystoreManager([keystore])
}
let endpoint = "https://goerli.infura.io/v3/MY_API"
let web3 = web3(provider: Web3HttpProvider(URL(string: endpoint)!)!)
web3.addKeystoreManager(keystoreManager)
let coldWalletAddress = EthereumAddress("0xMY_WALLET_ADDRESS")!
let walletAddress = EthereumAddress(wallet.address)! //
web3.transactionOptions.from = walletAddress
web3.transactionOptions.chainID = 5
let balanceResult = try! web3.eth.getBalance(address: walletAddress)
let balanceString = Web3.Utils.formatToEthereumUnits(balanceResult, toUnits: .eth, decimals: 3)!
I now wish to figure out how I can transfer some of this ETH into another wallet further after this code. I understand this process might require me to create a transaction, sign it with my key and then broadcast this transaction... I just don't know how to. Hoping to get a clear example as an answer!

Web3Swift Interaction with custom ABI

I am trying to interact with a smart contract I set up.
Basically the goal is to set from an iOS App 5 Parameters
projectTitle
projectLocation
projectStart
projectEnd
teamType
I want the user to set those parameters and write it on the ropsten testnetwork.
I also would like to get the contract information at a later point whenever the user feels for it.
my solidity code is working properly in remix and the contract is already deployed:
pragma solidity >=0.4.22 <0.7.0;
contract ProjectContent {
string public projectTitle;
string public projectLocation;
string public projectStart;
string public projectEnd;
string public teamType;
function projectContent(string initialProjectTitle, string initialProjectLocation, string initialProjectStart, string initialProjectEnd, string initialTeamType) public {
projectTitle = initialProjectTitle;
projectLocation = initialProjectLocation;
projectStart = initialProjectStart;
projectEnd = initialProjectEnd;
teamType = initialTeamType;
}
function setContract(string newProjectTitle, string newProjectLocation, string newProjectStart, string newProjectEnd, string newTeamType) public {
projectTitle = newProjectTitle;
projectLocation = newProjectLocation;
projectStart = newProjectStart;
projectEnd = newProjectEnd;
teamType = newTeamType;
}
function getProjectTitle() public view returns (string) {
return projectTitle;
}
function getProjectLocation() public view returns (string) {
return projectLocation;
}
function getProjectStart() public view returns (string) {
return projectStart;
}
function getProjectEnd() public view returns (string) {
return projectEnd;
}
function getTeamType() public view returns (string) {
return teamType;
}
}
My problem now is that I cannot figure out how to retrieve the data from the blockchain using the web3swift library. I am doing it like so now:
class ProjectContractViewController: UIViewController, HalfModalPresentable {
#IBOutlet weak var contractABIView: UITextView!
var halfModalTransitioningDelegate: HalfModalTransitioningDelegate?
var contractABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"initialProjectTitle\",\"type\":\"string\"},{\"name\":\"initialProjectLocation\",\"type\":\"string\"},{\"name\":\"initialProjectStart\",\"type\":\"string\"},{\"name\":\"initialProjectEnd\",\"type\":\"string\"},{\"name\":\"initialTeamType\",\"type\":\"string\"}],\"name\":\"projectContent\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newProjectTitle\",\"type\":\"string\"},{\"name\":\"newProjectLocation\",\"type\":\"string\"},{\"name\":\"newProjectStart\",\"type\":\"string\"},{\"name\":\"newProjectEnd\",\"type\":\"string\"},{\"name\":\"newTeamType\",\"type\":\"string\"}],\"name\":\"setContract\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectEnd\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectLocation\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectStart\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectTitle\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getTeamType\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectEnd\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectLocation\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectStart\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectTitle\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"teamType\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]"
let str = "0x6080604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806323a35e62146100bf57806337a4fc7d1461014f5780634a5736fd146101df5780634b04811e1461026f5780634e9d1281146102ff57806363afee221461038f578063775e6d451461051057806393ee0402146105a0578063c3e20c9f14610630578063d8045412146106c0578063dad375ff14610750578063f020cd19146108d1575b600080fd5b3480156100cb57600080fd5b506100d4610961565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101145780820151818401526020810190506100f9565b50505050905090810190601f1680156101415780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015b57600080fd5b50610164610a03565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101a4578082015181840152602081019050610189565b50505050905090810190601f1680156101d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156101eb57600080fd5b506101f4610aa1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610234578082015181840152602081019050610219565b50505050905090810190601f1680156102615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561027b57600080fd5b50610284610b3f565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102c45780820151818401526020810190506102a9565b50505050905090810190601f1680156102f15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561030b57600080fd5b50610314610bdd565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610354578082015181840152602081019050610339565b50505050905090810190601f1680156103815780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561039b57600080fd5b5061050e600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610c7b565b005b34801561051c57600080fd5b50610525610cf5565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561056557808201518184015260208101905061054a565b50505050905090810190601f1680156105925780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156105ac57600080fd5b506105b5610d97565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156105f55780820151818401526020810190506105da565b50505050905090810190601f1680156106225780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561063c57600080fd5b50610645610e39565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561068557808201518184015260208101905061066a565b50505050905090810190601f1680156106b25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156106cc57600080fd5b506106d5610ed7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156107155780820151818401526020810190506106fa565b50505050905090810190601f1680156107425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561075c57600080fd5b506108cf600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610f79565b005b3480156108dd57600080fd5b506108e6610ff3565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561092657808201518184015260208101905061090b565b50505050905090810190601f1680156109535780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b606060028054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156109f95780601f106109ce576101008083540402835291602001916109f9565b820191906000526020600020905b8154815290600101906020018083116109dc57829003601f168201915b5050505050905090565b60028054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a995780601f10610a6e57610100808354040283529160200191610a99565b820191906000526020600020905b815481529060010190602001808311610a7c57829003601f168201915b505050505081565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b375780601f10610b0c57610100808354040283529160200191610b37565b820191906000526020600020905b815481529060010190602001808311610b1a57829003601f168201915b505050505081565b60048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bd55780601f10610baa57610100808354040283529160200191610bd5565b820191906000526020600020905b815481529060010190602001808311610bb857829003601f168201915b505050505081565b60038054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c735780601f10610c4857610100808354040283529160200191610c73565b820191906000526020600020905b815481529060010190602001808311610c5657829003601f168201915b505050505081565b8460009080519060200190610c91929190611095565b508360019080519060200190610ca8929190611095565b508260029080519060200190610cbf929190611095565b508160039080519060200190610cd6929190611095565b508060049080519060200190610ced929190611095565b505050505050565b606060018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610d8d5780601f10610d6257610100808354040283529160200191610d8d565b820191906000526020600020905b815481529060010190602001808311610d7057829003601f168201915b5050505050905090565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610e2f5780601f10610e0457610100808354040283529160200191610e2f565b820191906000526020600020905b815481529060010190602001808311610e1257829003601f168201915b5050505050905090565b60008054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610ecf5780601f10610ea457610100808354040283529160200191610ecf565b820191906000526020600020905b815481529060010190602001808311610eb257829003601f168201915b505050505081565b606060038054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610f6f5780601f10610f4457610100808354040283529160200191610f6f565b820191906000526020600020905b815481529060010190602001808311610f5257829003601f168201915b5050505050905090565b8460009080519060200190610f8f929190611095565b508360019080519060200190610fa6929190611095565b508260029080519060200190610fbd929190611095565b508160039080519060200190610fd4929190611095565b508060049080519060200190610feb929190611095565b505050505050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561108b5780601f106110605761010080835404028352916020019161108b565b820191906000526020600020905b81548152906001019060200180831161106e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106110d657805160ff1916838001178555611104565b82800160010185558215611104579182015b828111156111035782518255916020019190600101906110e8565b5b5090506111119190611115565b5090565b61113791905b8082111561113357600081600090555060010161111b565b5090565b905600a165627a7a72305820458843a936d80ffe49dddb0955a0c1d56d0e15f994cd5ce31b386188b2724a790029"
var contractAddress = EthereumAddress("0x11A0c067d7481240dCA57457eff77fc98dEAdE0F")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func callContract(Password: String) {
// Get from address from private key
let formattedKey = Password.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey )!
// ### use [passKey]
let keystore = try! EthereumKeystoreV3(privateKey: dataKey, password: "")!
let keyData = try! JSONEncoder().encode(keystore.keystoreParams)
// let address = keystore.addresses!.first!.address
let address = keystore.addresses!.first!.address
let ethAddress = EthereumAddress(address)
let infura = Web3.InfuraMainnetWeb3()
// 1
let contract = infura.contract(contractABI, at: contractAddress, abiVersion: 2)
// 2
var options = TransactionOptions.defaultOptions
options.from = keystore.addresses!.first!
// 3
let data = Data.init(hex: str)
let transactionIntermediate = contract?.method("getProjectTitle", parameters: [address] as [AnyObject], extraData: data, transactionOptions: options)
// 4
let result = transactionIntermediate!.call(transactionOptions: options)
switch result {
// 5
case .success(let res):
let ans = res["0"] as! Bool
DispatchQueue.main.async {
completion(Result.Success(ans))
}
case .failure(let error):
DispatchQueue.main.async {
completion(Result.Error(error))
}
}
}
}
I get an error for the resultsaying:
"Call can throw, but it is not marked with 'try' and the error is not handled"
and in general I find it really hard to set up the interaction with a smart contract abi.
I am already using the web3swift functionality for sending transactions at it works like a charm.
Maybe someone knows how I can record information on the blockchain and get it using web3swift.
You are close. Starting with the error "Call can throw, but it is not marked with 'try' and the error is not handled" this is caused by trying to call a contract function without using the Try Catch pattern. Do to the way web3 library is designed this pattern is necessary for all write and call methods.
// Incorrect
let result = transactionIntermediate!.call(transactionOptions: options)
// Correct
do {
let result = try transactionIntermediate!.call(transactionOptions: options)
}catch{
print("Error trying to call method \(error)")
}
Additionally, I recommend using the DispatchQueue.main.async along with Promise Kit library when making your contract calls.
ABIs are hard to read and messy, don't recommend using it to help find callable methods and parameters within the contract. Instead I would have the contract open along side Xcode and through the use of either an enum or struct containing all the contract methods that are going to be used.
// Methods available within the contract
enum ContractMethods:String {
case projectContract = "projectContent"
case setContract = "setContract"
case getProjectTitle = "getProjectTitle"
case getProjectLocation = "getProjectLocation"
case getProjectStart = "getProjectStart"
case getProjectEnd = "getProjectEnd"
case getTeamType = "getTeamType"
}
// Usage
ContractMethods.setContract.rawValue
I moved the ABI to a separate file within xcode to keep it clean. Here is link to the file.
Here is a good example to help get you started. Check out my GitHub repo for the improved version.
import UIKit
import web3swift
import PromiseKit
struct Wallet {
let address: String
let data: Data
let name:String
let isHD:Bool
}
struct HDKey {
let name:String?
let address:String
}
var password = "" // leave empty for ganache or use your wallet password
let privateKey = "<PrivateKey>" // Private key of wallet
let walletName = "MyWallet"
let contractAddress = "<ContractAddress>" // 0x11A0c067d7481240dCA57457eff77fc98dEAdE0F
let endpoint = URL(string:"http://127.0.0.1:7545")! // Im using Ganache but it might look like endpoint = URL(string:"https://rinkeby.infura.io/v3/<APIKEY>")!
let abiVersion = 2
class ViewController: UIViewController {
// Mock data used within contract
let projectTitle = "HouseSiding"
let projectLocation = "299 Race Ave. Dacula, GA 30019"
let projectStart = "May 14, 2021"
let projectEnd = "June 15, 2021"
let teamType = "Collaboration"
var web3:web3?
var contract:web3.web3contract?
override func viewDidLoad() {
super.viewDidLoad()
// 1. Create wallet using a private key
let formattedKey = privateKey.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey)!
let keyStore = try! EthereumKeystoreV3(privateKey:dataKey, password: password)!
let keyData = try! JSONEncoder().encode(keyStore.keystoreParams)
let address = keyStore.addresses!.first!.address
let wallet = Wallet(address: address, data: keyData, name: walletName, isHD: false)
// 2. Construct web3 and keystoreManager
do {
web3 = try Web3.new(endpoint)
let data = wallet.data
var keystoreManager: KeystoreManager
if wallet.isHD {
let keystore = BIP32Keystore(data)!
keystoreManager = KeystoreManager([keystore])
}else{
let keystore = EthereumKeystoreV3(data)!
keystoreManager = KeystoreManager([keystore])
}
print(keystoreManager.addresses)
web3!.addKeystoreManager(keystoreManager)
let ethContractAddress = EthereumAddress(contractAddress, ignoreChecksum: true)!
contract = web3!.contract(contractABI, at: ethContractAddress, abiVersion: abiVersion)!
}catch{
print ("Failed to construct contract and/or keystoreManager \(error)")
}
// 3. Create and callout a contract method
//let parameters = [projectTitle,projectLocation,projectStart,projectEnd,teamType] as [AnyObject] // parameters used to created a new project
let parameters = [] as [AnyObject] // no parameters
let response = Promise<Any> { seal in
DispatchQueue.global().async {
// Catch errors within async call
do {
// No extra data for method call
let extraData: Data = Data()
// Options for method call
var options = TransactionOptions.defaultOptions
options.from = EthereumAddress(wallet.address)! // current wallet address
// Leave automatic for gas
options.gasPrice = .automatic
options.gasLimit = .automatic
// Calling get Project title from contract
// NOTE: First call setContract with parameters
let tx = self.contract!.method("getProjectTitle",
parameters: parameters,
extraData: extraData,
transactionOptions: options)
// Depending on the type of call a password might be needed
//if password != nil {
//let result = try tx!.send(password: password)
// seal.resolve(.fulfilled(true))
//}else{
let result = try tx!.call()
// fulfill are result from contract
let anyResult = result["0"] as Any
seal.resolve(.fulfilled(anyResult))
//}
}catch {
// error
seal.reject(error)
}
}
}
response.done({result in
print(result) // Optional(HouseSiding)
})
}
}

Converting app from SQLite.swift to GRDB.swift Second Question

I am continuing the conversion of a number of my apps from SQLite.sift to GRDB.swift.
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB. This was suggested in a reply to my first post on this subject.
struct FigureList: Codable, FetchableRecord
{
let figID: Int64
var notes: String?
var theDescription: String
var obrienNum: String
var manufNum: String?
var catagory: String?
}
This is the piece of code I'm looking for help to redo so it will work with GRDB. My apps use this type of code to build arrays from the database table. Pretty standard process.
static func getFigureList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = Table(gTheCollection)
let figID = Expression<Int64>("FigureID")
let notes = Expression<String>("Notes")
let theDescription = Expression<String>("theDescription")
let obrienNum = Expression<String>("ObrienNum")
let manufNum = Expression<String>("ManufNum")
let theCatagory = Expression<String>("Category")
do {
for figures in try Database.shared.databaseConnection!.prepare(theTable.order(obrienNum)) {
theArray.append(FigureList(figID: figures[figID],
notes: figures[notes],
theDescription: figures[theDescription],
obrienNum: figures[obrienNum],
manufNum: figures[manufNum],
catagory: figures[theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
This is what I have come up with so far. It doesn't produce any warnings or errors but then again I'm pretty sure it's not total correct. Again, thanks in advance for any help.
static func getFigList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = gTheCollection
let figID = Column("FigureID")
let notes = Column("Notes")
let theDescription = Column("theDescription")
let obrienNum = Column("ObrienNum")
let manufNum = Column("ManufNum")
let theCatagory = Column("Category")
do {
try Database.shared.databaseConnection!.read { db in
let figures = try Row.fetchOne(db, sql: "SELECT * FROM = ? ORDER BY ObrienNum", arguments: [theTable])
theArray.append(FigureList.init(figID: figures![figID],
notes: figures![notes],
theDescription: figures![theDescription],
obrienNum: figures![obrienNum],
manufNum: figures![manufNum],
catagory: figures![theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB.
Good. Now it's very simple:
static func getFigList() throws -> [FigureList] {
return try Database.shared.databaseConnection!.read { db in
// SELECT * FROM FigureList ORDER BY ObrienNum
return FigureList.order(Column("ObrienNum")).fetchAll(db)
}
}

Proper Way to initialize a model class with data gathered from the system (macOS)

I need certain types of system information available to configure my app. I want to achieve this by having a model class which makes the information available. I know how to get the desired information but i am struggling to store this gathered information in the properties of my model class.
i want to be able to:
let sysInfoModel = SysInfoModel()
let installedOsVersion = sysInfoModel.osVersion
The model class should be instantiated from the vieController which needs this information to set up.
I think my whole attempt is wrong to achieve such thing. What is the proper way to do so?
class SysInfoModel {
// Properties
let macModel: String?
let osVersion: String?
// Initialization
init() {
if let actualModel = getMacModel() { //ERROR: 'self' used in method call 'getMacModel' before all stored properties are initialized
self.macModel = actualModel
} else {
macModel = "Hardware model not found"
}
if let actualOsVersion = getOsVersion() { // ERROR: 'self' used in method call 'getOsVersion' before all stored properties are initialized
osVersion = actualOsVersion
} else {
osVersion = "OS Version not available"
}
}
// Returns the hardware model identifier as a string
func getMacModel() -> String? {
var modelIdentifier: String?
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
modelIdentifier = String(data: modelData, encoding: .utf8)
}
IOObjectRelease(service)
return modelIdentifier
}
// Returns the OS Version as a String
func getOsVersion() -> String? {
let osVersion: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion
let osVersionString = String("\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)")
return osVersionString
}
}
Update after matt's answer:
// Properties
var askSystem: AskTheSystem
let macModel: String?
let osVersion: String?
init() {
askSystem = AskTheSystem()
if let actualModel = askSystem.getMacModel() {
self.macModel = actualModel
} else {
macModel = "Hardware model not found"
}
if let actualOsVersion = askSystem.getOsVersion() {
osVersion = actualOsVersion
} else {
osVersion = "OS Version not available"
}
}
I did, there is the possibility to put the functions getMacModel() & getOsVersion() in a separate class called AskTheSystem. Instantiate it in the SysInfoModel.
It works but is this good practice ? i don't see the point in adding another class just for those functions ? What is the proper way to do so ?
I agree with matt, proper is a judgement.
A reasonable solution are lazy instantiated properties, both values are retrieved once when the property is accessed the first time
class SysInfoModel {
// Returns the hardware model identifier as a string
lazy var macModel : String = {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
defer{IOObjectRelease(service)}
guard let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data else {
return "Hardware model not found"
}
return String(data: modelData, encoding: .utf8)!
}()
// Returns the OS Version as a String
lazy var osVersion : String = {
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
return "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
}()
}

onNext not getting called unit testing RxCocoa Driver

I'm attempting to write a unit test for Driver from RxCocoa library. Here's my simplified implementation code:
struct LoginViewModel {
var username: Driver<String?>!
var password: Driver<String?>!
var loginTaps: Driver<Void>!
func login() -> Driver<LoginResult> {
let credentials = Driver.combineLatest(username, password) { ($0, $1) }
let latestCredentials = loginTaps.withLatestFrom(credentials)
return latestCredentials.flatMapLatest { (username, password) in
.just(.success)
}
}
}
And here's the Quick/Nimble unit test I'm attempting to pass:
let disposeBag = DisposeBag()
var capturedLoginResult = LoginResult.failed
loginViewModel.username = Driver.just("some username")
loginViewModel.password = Driver.just("some password")
loginViewModel.loginTaps = Driver.just()
loginViewModel.login().drive(onNext: { loginResult in
capturedLoginResult = loginResult
}).addDisposableTo(disposeBag)
expect(capturedLoginResult == .success)
Above expect says that capturedLoginResult is still .failed. It appears as though element from return latestCredentials.flatMapLatest { (username, password) in .just(.success) } is not getting received by the .drive(onNext: ) in the test.
If the implementation of login is just:
func login() -> Driver<LoginResult> {
return .just(.success)
}
The test passes.
Any thoughts on what's happening here? Thanks!
I don't know exactly where in Rx's source, but my guess is that an operator you are using is switching scheduler. Because of this, the subscription made with drive(onNext:) is not trigger immediately.
RxSwift provides a good API for testing our observables, through the RxTest package. You could rewrite your tests to take advantage of it.
let scheduler = TestScheduler(initialClock: 0)
let username = scheduler.createHotObservable([next(220, "username"), completed(20)])
let password = scheduler.createHotObservable([next(230, "p4ssw0rd"), completed(20)])
let loginTaps = scheduler.createHotObservable([next(240), completed(20)])
let recordObserver = scheduler.start(300) { () -> Observable<LoginResult> in
let loginViewModel = LoginViewModel()
loginViewModel.username = username.asDriver(onErrorJustReturn: "")
loginViewModel.password = username.asDriver(onErrorJustReturn: "")
loginViewModel.loginTaps = loginTaps.asDriver(onErrorJustReturn: ())
return loginViewModel.login().asObservable()
}
let expectedEvents: [Recorded<Event<LoginResult>>] = [
next(240, Login.success)
]
expect(recordObserver.events) == (expectedEvents)