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

951 Views Asked by At

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.

1

There are 1 best solutions below

0
Cublax On BEST ANSWER

The smart contract interaction requires a few initialisations steps:

  1. Your wallet
  2. KeyStoreManager
  3. 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 :)