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.
NoteFollowing example is using Swift 5 with XCode 11. IOS demo will be open-sourced soon.
Swift
// AppDelegate.swift

import MagicSDK
import UIKit

@UIApplicationMain
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  // assign the newly created Magic instance to shared property
  Magic.shared = Magic("YOUR_PUBLISHABLE_API_KEY");

  return true
}
Swift
// ViewController.swift

import UIKit
import MagicSDK
import MagicSDK_Web3

class Web3ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
}

Use Different Networks

Testnet

Goerli Block Explorer: https://goerli.etherscan.io Goerli Testnet Faucet: https://goerlifaucet.com
Swift
// AppDelegate.swift
import MagicSDK

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  // assign to Magic singleton
  Magic.shared = Magic("YOUR_PUBLISHABLE_API_KEY", EthNetwork.rinkeby);

  return true
}

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. You can update the policy in the settings page of the dashboard with your custom URL.
NoteNote: 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
// AppDelegate.swift
import MagicSDK

// assign to Magic singleton
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  let config = CustomNodeConfiguration(rpcUrl: "https://alchemy.io", chainId: 1)
  Magic.shared = Magic(apiKey: "API_KEY", customNode: config);

  return true
}
ImportantDo 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
public enum EthNetwork: String {
  case mainnet
  case goerli
}

Common Methods

Send Transaction

Swift
import MagicSDK
import MagicSDK_Web3

class Web3ViewController: UIViewController {
  var web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  // After user is successfully authenticated
  func sendTransaction() {
    guard let account = self.account else { return }

    // Construct a transaction
    let transaction = EthereumTransaction(
      from: account, // from Get User Info section
      to: EthereumAddress(hexString: "0xE0cef4417a772512E6C95cEf366403839b0D6D6D"),
      value: EthereumQuantity(quantity: 1.gwei)
    )

    // Submit transaction to the blockchain
    web3.eth.sendTransaction(transaction: transaction).done { (transactionHash) in
      print(transactionHash.hex())
    }.catch { error in
      print(error.localizedDescription)
    }
  }
}

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
import MagicSDK
import MagicSDK_Web3
import PromiseKit

class ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  func ethSign() {
    guard let account = self.account else { return }

    let message = try! EthereumData("Hello World".data(using: .utf8)!)
    web3.eth.sign(from: account, message: message).done({ result in
      print(result.hex())
    })
  }
}

Sign Typed Data Legacy (V1)

Swift
import MagicSDK
import MagicSDK_Web3
import PromiseKit

class Web3ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  func SignTypedDataLegacy() {
    guard let account = self.account else { return }

    let payload = EIP712TypedDataLegacyFields(type: "string", name: "Hello from Magic Labs", value: "This message will be signed by you")

    web3.eth.signTypedDataLegacy(account: account, data: [payload]).done({ result in
      print(result.hex())
    })
  }
}

Sign Typed Data v3(EIP 712)

Swift
import MagicSDK
import MagicSDK_Web3

class Web3ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  func SignTypedData() {
    guard let account = self.account else { return }

    do {
      let json = """
        {"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!"}}
      """.data(using: .utf8)!
      let typedData = try JSONDecoder().decode(EIP712TypedData.self, from: json)

      web3.eth.signTypedData(account: account, data: typedData).done({ result in
        print(result.hex())
      })
    } catch {
      print(error.localizedDescription)
    }
  }
}

Sign Typed Data V4 (EIP 712)

Swift
import MagicSDK
import MagicSDK_Web3

class Web3ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  func SignTypedData() {
    guard let account = self.account else { return }

    do {
      let json = """
        {"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[]"}]}}
      """.data(using: .utf8)!
      let typedData = json.data(using: .utf8)!
      let typedDataV4 = try JSONDecoder().decode(EIP712TypedData.self, from: typedData)
      web3.eth.signTypedDataV4(account: address, data: typedDataV4).done({ response in
        print(response.hex())
      })
    // Catch decoding error
    } catch let DecodingError.typeMismatch(type, context)  {
      print("Type '\(type)' mismatch:", context.debugDescription)
      print("codingPath:", context.codingPath)
    } catch {
      self.showResult(error.localizedDescription)
    }
  }
}

Get User Info

Swift
import MagicSDK
import MagicSDK_Web3
import PromiseKit

class Web3ViewController: UIViewController {
  var web3 = Web3(provider: Magic.shared.rpcProvider)

  // After user is successfully authenticated
  @IBOutlet weak var accountLabel: UILabel!

  func getAccount() {
    firstly {
      // Get user's Ethereum public address
      web3.eth.accounts()
    }.done { accounts -> Void in
      if let account = accounts.first {
        // Set to UILa
        self.accountLabel.text = account.hex(eip55: false)
      } else {
        print("No Account Found")
      }
    }.catch { error in
      print("Error loading accounts and balance: \(error)")
    }
  }
}

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.
Solidity
pragma solidity ^0.5.10;

contract HelloWorld {
  string public message;

  constructor(string memory initMessage) public {
    message = initMessage;
  }

  function update(string memory newMessage) public {
    message = newMessage;
  }
}

Deploy Contract

Swift
import MagicSDK
import MagicSDK_Web3

class Web3ViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?
  // After user is successfully authenticated

  func deployContract() {
    guard let account = self.account else { return }

    do {
      let contractABI = """
        [{"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"}]
      """.data(using: .utf8)!
      let contractByteCode = try EthereumData("0x608060405234801561001057600080fd5b5060405161047f38038061047f8339818101604052602081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190505050806000908051906020019061009c9291906100a3565b5050610148565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100e457805160ff1916838001178555610112565b82800160010185558215610112579182015b828111156101115782518255916020019190600101906100f6565b5b50905061011f9190610123565b5090565b61014591905b80821115610141576000816000905550600101610129565b5090565b90565b610328806101576000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c0100000000000000000000000000000000000000000000000000000000900480633d7403a314610058578063e21f37ce14610113575b600080fd5b6101116004803603602081101561006e57600080fd5b810190808035906020019064010000000081111561008b57600080fd5b82018360208201111561009d57600080fd5b803590602001918460018302840111640100000000831117156100bf57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610196565b005b61011b6101b0565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561015b578082015181840152602081019050610140565b50505050905090810190601f1680156101885780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101ac92919061024e565b5050565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102465780601f1061021b57610100808354040283529160200191610246565b820191906000526020600020905b81548152906001019060200180831161022957829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028f57805160ff19168380011785556102bd565b828001600101855582156102bd579182015b828111156102bc5782518255916020019190600101906102a1565b5b5090506102ca91906102ce565b5090565b6102f091905b808211156102ec5760008160009055506001016102d4565b5090565b9056fea265627a7a7230582003ae1ef5a63bf058bfd2b31398bdee39d3cbfbb7fbf84235f4bc2ec352ee810f64736f6c634300050a0032")

      /// Create Contract instance
      let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: nil)

      /// Deploy contract
      guard let invocation = contract.deploy(byteCode: contractByteCode) else { return }
      invocation.send(from: self.account!, gas: 1025256, gasPrice: 0) { (hash, error) in
        print(hash?.hex() ?? "Missing Hash")
        print(error?.localizedDescription ?? "Error")
      }
    } catch {
      print(error.localizedDescription)
    }
  }
}

Read From Contract

Swift
import MagicSDK
import MagicSDK_Web3

class MagicViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  // After user is successfully authenticated

  func getMessage() {
    do {
      /// Construct contract instance
      let contractABI = """
        [{"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"}]
      """.data(using: .utf8)!
      let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: EthereumAddress(ethereumValue: "0x8b211dfebf490a648f6de859dfbed61fa22f35e0"))

      /// contract call
      contract["message"]?().call() { response, error in
        if let response = response, let message = response[""] as? String {
          print(message.description)
        } else {
        print(error?.localizedDescription ?? "Failed to get response")
        }
      }
    } catch {
      /// Error handling
      print(error.localizedDescription)
    }
  }
}

Write to Contract

Swift
import MagicSDK
import MagicSDK_Web3

class MagicViewController: UIViewController {
  let web3 = Web3(provider: Magic.shared.rpcProvider)
  var account: EthereumAddress?

  // After user is successfully authenticated

  func writeMessage() {
    guard let account = self.account else { return }

    do {
      /// contract instance
      let contractABI = """
        [{"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"}]
      """.data(using: .utf8)!
      let contract = try web3.eth.Contract(json: contractABI, abiKey: nil, address: EthereumAddress(ethereumValue: "0x8b211dfebf490a648f6de859dfbed61fa22f35e0"))

      /// contract call
      guard let transaction = contract["update"]?("NEW_MESSAGE").createTransaction(
        nonce: 0,
        from: account,
        value: 0,
        gas: EthereumQuantity(150000),
        gasPrice: EthereumQuantity(quantity: 21.gwei)
        ) else { return }

      web3.eth.sendTransaction(transaction: transaction).done({ txHash in
        print(txHash.hex())
      }).catch{ error in
        print(error.localizedDescription)
      }
    } catch {
      print(error.localizedDescription)
    }
  }
}

Resources