How to Integrate with the Ethereum Blockchain using iOS

How to Integrate with the Ethereum Blockchain using iOS

#Installation

To interact with the Ethereum blockchain, Magic iOS SDK embeds Web3.swift as a sub-dependency. No more extra dependency is needed.

#Initialization

The Magic class is the entry-point to the Magic SDK. It must be instantiated with a Magic publishable key.

note

Following example is using Swift 5 with XCode 11. IOS demo will be open-sourced soon.

Swift
01// AppDelegate.swift
02
03import MagicSDK
04import UIKit
05
06@UIApplicationMain
07func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
08
09    // assign the newly created Magic instance to shared property
10    Magic.shared = Magic("YOUR_PUBLISHABLE_API_KEY");
11
12    return true
13}
Swift
01// ViewController.swift
02
03import UIKit
04import MagicSDK
05import MagicSDK_Web3
06
07class Web3ViewController: UIViewController {
08
09    let web3 = Web3(provider: Magic.shared.rpcProvider)
10}

#Use Different Networks

#Testnet

Goerli Block Explorer: https://goerli.etherscan.io

Goerli Testnet Faucet: https://goerlifaucet.com

Swift
01// AppDelegate.swift
02import MagicSDK
03
04func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
05
06    // assign to Magic singleton
07    Magic.shared = Magic("YOUR_PUBLISHABLE_API_KEY", EthNetwork.rinkeby);
08
09    return true
10}

#Custom Node

You can allow specific URLs to interact with the Magic SDK, such as a custom RPC URL to send transactions to your node. The Content Security Policy (CSP) of a browser dictates what resources can be loaded. If you're used a dedicated wallet, you can update the policy in the settings page of the dashboard with your custom URL. If you're using a universal wallet, please reach out to support to get your URL added.

note

Note: the use of a custom node will require the RPC URL to the project's Content Security Policy from your Magic dashboard. Refer to the CSP documentation.

Swift
01// AppDelegate.swift
02import MagicSDK
03
04// assign to Magic singleton
05func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
06
07    let config = CustomNodeConfiguration(rpcUrl: "https://alchemy.io", chainId: 1)
08    Magic.shared = Magic(apiKey: "API_KEY", customNode: config);
09
10    return true
11}
important

Do not set the custom nodes to local IP address (E.x. "http://127.0.0.1"). Local IP will point to the network environment inside mobile device / simulator

#Associated Class

CustomNodeConfiguration(rpcUrl: String, chainId: Int?)

  • rpcUrl :Your own node URL
  • chainId : Your own node's chainId

EthNetwork

Swift
01public enum EthNetwork: String {
02    case mainnet
03    case goerli
04}

#Common Methods

#Send Transaction

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class Web3ViewController: UIViewController {
05
06    var web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08
09    // ⭐️ After user is successfully authenticated
10    func sendTransaction() {
11
12        guard let account = self.account else { return }
13
14        // Construct a transaction
15        let transaction = EthereumTransaction(
16            from: account, // from Get User Info section
17            to: EthereumAddress(hexString: "0xE0cef4417a772512E6C95cEf366403839b0D6D6D"),
18            value: EthereumQuantity(quantity: 1.gwei)
19        )
20
21        // Submit transaction to the blockchain
22        web3.eth.sendTransaction(transaction: transaction).done { (transactionHash) in
23            print(transactionHash.hex())
24        }.catch { error in
25            print(error.localizedDescription)
26        }
27    }
28}

#Sign Message

Magic iOS SDK extends the functionality from Web3.swift to allow developers to sign Typed Data. Make sure to import MagicSDK while using these functions.

#Eth Sign

Swift
01import MagicSDK
02import MagicSDK_Web3
03import PromiseKit
04
05class ViewController: UIViewController {
06
07    let web3 = Web3(provider: Magic.shared.rpcProvider)
08    var account: EthereumAddress?
09
10    func ethSign() {
11        guard let account = self.account else { return }
12
13        let message = try! EthereumData("Hello World".data(using: .utf8)!)
14        web3.eth.sign(from: account, message: message).done({ result in
15            print(result.hex())
16        })
17    }
18}

#Sign Typed Data Legacy (V1)

Swift
01import MagicSDK
02import MagicSDK_Web3
03import PromiseKit
04
05class Web3ViewController: UIViewController {
06
07    let web3 = Web3(provider: Magic.shared.rpcProvider)
08    var account: EthereumAddress?
09
10    func SignTypedDataLegacy() {
11        guard let account = self.account else { return }
12
13        let payload = EIP712TypedDataLegacyFields(type: "string", name: "Hello from Magic Labs", value: "This message will be signed by you")
14
15        web3.eth.signTypedDataLegacy(account: account, data: [payload]).done({ result in
16            print(result.hex())
17        })
18    }
19}

#Sign Typed Data v3(EIP 712)

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class Web3ViewController: UIViewController {
05
06    let web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08
09    func SignTypedData() {
10        guard let account = self.account else { return }
11
12        do {
13            let json = """
14                {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"verifyingContract","type":"address"}],"Greeting":[{"name":"contents","type":"string"}]},"primaryType":"Greeting","domain":{"name":"Magic","version":"1","verifyingContract":"0xE0cef4417a772512E6C95cEf366403839b0D6D6D"},"message":{"contents":"Hello, from Magic!"}}
15            """.data(using: .utf8)!
16            let typedData = try JSONDecoder().decode(EIP712TypedData.self, from: json)
17
18            web3.eth.signTypedData(account: account, data: typedData).done({ result in
19                print(result.hex())
20            })
21        } catch {
22            print(error.localizedDescription)
23        }
24    }
25}

#Sign Typed Data V4 (EIP 712)

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class Web3ViewController: UIViewController {
05
06    let web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08
09    func SignTypedData() {
10        guard let account = self.account else { return }
11
12        do {
13            let json = """
14            {"domain":{"chainId":1,"name":"Ether Mail","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","version":"1"},"message":{"contents":"Hello, Bob!","from":{"name":"Cow","wallets":["0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826","0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]},"to":[{"name":"Bob","wallets":["0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57","0xB0B0b0b0b0b0B000000000000000000000000000"]}]},"primaryType":"Mail","types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Group":[{"name":"name","type":"string"},{"name":"members","type":"Person[]"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person[]"},{"name":"contents","type":"string"}],"Person":[{"name":"name","type":"string"},{"name":"wallets","type":"address[]"}]}}
15            """.data(using: .utf8)!
16            let typedData = json.data(using: .utf8)!
17            let typedDataV4 = try JSONDecoder().decode(EIP712TypedData.self, from: typedData)
18            web3.eth.signTypedDataV4(account: address, data: typedDataV4).done({ response in
19                print(response.hex())
20            })
21        // Catch decoding error
22        } catch let DecodingError.typeMismatch(type, context)  {
23               print("Type '\(type)' mismatch:", context.debugDescription)
24               print("codingPath:", context.codingPath)
25        } catch {
26            self.showResult(error.localizedDescription)
27        }
28    }
29}

#Get User Info

Swift
01import MagicSDK
02import MagicSDK_Web3
03import PromiseKit
04
05class Web3ViewController: UIViewController {
06
07    var web3 = Web3(provider: Magic.shared.rpcProvider)
08
09    // ⭐️ After user is successfully authenticated
10    @IBOutlet weak var accountLabel: UILabel!
11
12    func getAccount() {
13
14        firstly {
15            // Get user's Ethereum public address
16            web3.eth.accounts()
17        }.done { accounts -> Void in
18            if let account = accounts.first {
19                // Set to UILa
20                self.accountLabel.text = account.hex(eip55: false)
21            } else {
22                print("No Account Found")
23            }
24        }.catch { error in
25            print("Error loading accounts and balance: \(error)")
26        }
27    }
28}

#Smart Contract

In this example, we'll be demonstrating how to use Magic with Web3.swift to interact with Solidity smart contracts. The simple Hello World contract allows anyone to read and write a message to it.

Javascript
01pragma solidity ^0.5.10;
02
03contract HelloWorld {
04
05  string public message;
06
07  constructor(string memory initMessage) public {
08    message = initMessage;
09  }
10
11  function update(string memory newMessage) public {
12    message = newMessage;
13  }
14}

#Deploy Contract

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class Web3ViewController: UIViewController {
05
06    let web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08            // ⭐️ After user is successfully authenticated
09
10    func deployContract() {
11
12        guard let account = self.account else { return }
13
14        do {
15            let contractABI = """
16                [{"constant":false,"inputs":[{"name":"newMessage","type":"string"}],"name":"update","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"message","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"initMessage","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
17            """.data(using: .utf8)!
18            let contractByteCode = try EthereumData("0x608060405234801561001057600080fd5b5060405161047f38038061047f8339818101604052602081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190505050806000908051906020019061009c9291906100a3565b5050610148565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100e457805160ff1916838001178555610112565b82800160010185558215610112579182015b828111156101115782518255916020019190600101906100f6565b5b50905061011f9190610123565b5090565b61014591905b80821115610141576000816000905550600101610129565b5090565b90565b610328806101576000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c0100000000000000000000000000000000000000000000000000000000900480633d7403a314610058578063e21f37ce14610113575b600080fd5b6101116004803603602081101561006e57600080fd5b810190808035906020019064010000000081111561008b57600080fd5b82018360208201111561009d57600080fd5b803590602001918460018302840111640100000000831117156100bf57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610196565b005b61011b6101b0565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561015b578082015181840152602081019050610140565b50505050905090810190601f1680156101885780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101ac92919061024e565b5050565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102465780601f1061021b57610100808354040283529160200191610246565b820191906000526020600020905b81548152906001019060200180831161022957829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028f57805160ff19168380011785556102bd565b828001600101855582156102bd579182015b828111156102bc5782518255916020019190600101906102a1565b5b5090506102ca91906102ce565b5090565b6102f091905b808211156102ec5760008160009055506001016102d4565b5090565b9056fea265627a7a7230582003ae1ef5a63bf058bfd2b31398bdee39d3cbfbb7fbf84235f4bc2ec352ee810f64736f6c634300050a0032")
19
20            /// Create Contract instance
21            let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: nil)
22
23            /// Deploy contract
24            guard let invocation = contract.deploy(byteCode: contractByteCode) else { return }
25            invocation.send(from: self.account!, gas: 1025256, gasPrice: 0) { (hash, error) in
26                    print(hash?.hex() ?? "Missing Hash")
27                    print(error?.localizedDescription ?? "Error")
28                }
29        } catch {
30            print(error.localizedDescription)
31        }
32    }
33}

#Read From Contract

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class MagicViewController: UIViewController {
05
06    let web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08
09    // ⭐️ After user is successfully authenticated
10
11    func getMessage() {
12
13        do {
14
15            /// Construct contract instance
16            let contractABI = """
17                    [{"constant":false,"inputs":[{"name":"newMessage","type":"string"}],"name":"update","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"message","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"initMessage","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
18                """.data(using: .utf8)!
19            let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: EthereumAddress(ethereumValue: "0x8b211dfebf490a648f6de859dfbed61fa22f35e0"))
20
21            /// contract call
22            contract["message"]?().call() { response, error in
23                if let response = response, let message = response[""] as? String {
24                    print(message.description)
25                } else {
26                    print(error?.localizedDescription ?? "Failed to get response")
27                }
28            }
29        } catch {
30            /// Error handling
31            print(error.localizedDescription)
32        }
33    }
34}

#Write to Contract

Swift
01import MagicSDK
02import MagicSDK_Web3
03
04class MagicViewController: UIViewController {
05
06    let web3 = Web3(provider: Magic.shared.rpcProvider)
07    var account: EthereumAddress?
08
09    // ⭐️ After user is successfully authenticated
10
11    func writeMessage() {
12
13        guard let account = self.account else { return }
14
15        do {
16            /// contract instance
17            let contractABI = """
18                [{"constant":false,"inputs":[{"name":"newMessage","type":"string"}],"name":"update","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"message","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"initMessage","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
19            """.data(using: .utf8)!
20            let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: EthereumAddress(ethereumValue: "0x8b211dfebf490a648f6de859dfbed61fa22f35e0"))
21
22            /// contract call
23            guard let transaction = contract["update"]?("NEW_MESSAGE").createTransaction(
24                nonce: 0,
25                from: account,
26                value: 0,
27                gas: EthereumQuantity(150000),
28                gasPrice: EthereumQuantity(quantity: 21.gwei)
29                ) else { return }
30        
31            web3.eth.sendTransaction(transaction: transaction).done({ txHash in
32                print(txHash.hex())
33            }).catch{ error in
34                print(error.localizedDescription)
35            }
36        } catch {
37            print(error.localizedDescription)
38        }
39    }
40}

#Resources