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.
Following example is using Swift 5 with XCode 11. IOS demo will be open-sourced soon.
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}
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
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: 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.
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}
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 URLchainId
: Your own node's chainId
EthNetwork
01public enum EthNetwork: String {
02 case mainnet
03 case goerli
04}
#Common Methods
#Send Transaction
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
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)
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)
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)
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
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.
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
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
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
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}