Adding Wallet Functionalities

The page provides instructions on how to add wallet/Bolt functionalities to the sample app.

Wallet Directories

The following directories lies inside the xcode project. Here is the xcode screenshot for example:

Description for the directories and files are provided below:

--Bolt(directory)  // Directory Containing all Wallet Functionalities
  --App(directory)  
    --BoltHome.swift  
    --TransactionDetails.swift  
  --Model(directory) //Directory Containing Models that can be utilized by Bolt App Directory
    --Balance.swift(file) 
    --Transaction.swift(file)
    --Wallet.swift(file)  
  --View(directory) // Defininig View for Bolt/Wallet functionalities 
    --AvailableBalanceBlock.swift(file)
    --WalletActionStack.swift(file) 
  --ViewModel(directory) // Directory Containing Models that can be utilized by View Directory
    --BoltViewModel.swift(file)  

Describing Code in Directories

Bolt/App/BoltHome.swift
//
//  BoltHome.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 29/12/22.
//

import SwiftUI

struct BoltHome: View {
    @EnvironmentObject var boltVM: BoltViewModel
    @State private var sortOrder = [KeyPathComparator(\Transaction.creationDate)]
    var body: some View {
        GeometryReader { gr in
            ZStack(alignment: .bottom) {
                VStack(alignment: .leading) {
                    
                    AvailableBalanceBlock()
                    
                    WalletActionStack(width: gr.size.width)
                    
                    HStack(spacing: 15) {
                        Text("Transaction Hash")
                        Spacer()
                        Text("Transaction Date").layoutPriority(1)
                        Image(systemName: "chevron.right").opacity(0)
                    }
                    .bold()
                    ScrollView(showsIndicators: false) {
                        ForEach(Array(boltVM.transactions.sorted().enumerated()),id:\.offset) { index, txn in
                            NavigationLink(destination: TransactionDetails(transaction: txn)) {
                                TransactionRow(index: index, txn: txn)
                            }
                        }
                        .listStyle(.plain)
                    }
                }
                if boltVM.presentPopup {
                    ZCNToast(type: boltVM.popup,presented: $boltVM.presentPopup)
                }
            }
        }
        .padding(20)
        .environmentObject(boltVM)
        .navigationTitle(Text("Bolt"))
        .navigationBarTitleDisplayMode(.large)
        .onAppear(perform: boltVM.getTransactions)
        .alert("Recieve ZCN", isPresented: $boltVM.presentReceiveView,actions: {recievAlert}) {
            Text("Wallet address\n\(Utils.wallet?.client_id ?? "")")
        }
        .alert("Send ZCN", isPresented: $boltVM.presentSendView,actions: {sendAlert})
        .alert("Error", isPresented: $boltVM.presentErrorAlert) {
            Text(boltVM.alertMessage)
        }
    }
    
    @ViewBuilder func TransactionRow(index:Int,txn:Transaction) -> some View {
        HStack(spacing: 15) {
            Text("\(index+1). \(txn.hash)")
            Spacer()
            Text(Date(timeIntervalSince1970: txn.creationDate/1e9).formatted()).layoutPriority(1)
            Image(systemName: "chevron.right")
        }
        .foregroundColor(txn.status == 1 ? .primary : .pink)
        .lineLimit(1)
        .padding(.vertical,10)
    }
    
    @ViewBuilder var sendAlert: some View {
        
        TextField("client ID", text: $boltVM.clientID)
        TextField("amount", text: $boltVM.amount)
        
        Button("Send",action:boltVM.sendZCN)
        //.disabled(!boltVM.clientID.isValidAddress || !boltVM.amount.isValidNumber)
    }
    
    @ViewBuilder var recievAlert: some View {
        Button("Copy",action:boltVM.copyClientID)
    }
    
}

struct BoltHome_Previews: PreviewProvider {
    static var previews: some View {
        var boltVM: BoltViewModel = {
            let boltVM = BoltViewModel()
            boltVM.transactions = [Transaction(hash: "dhudhiduididg", creationDate: 93778937837837, status: 1),Transaction(hash: "dhudheijeioeiduididg", creationDate: 937789337837, status: 1),Transaction(hash: "dhudhidehieeuididg", creationDate: 9377893474837, status: 2)]
            return boltVM
        }()
        
        BoltHome()
            .environmentObject(boltVM)
    }
}

Describing Code

  • Line 8 import SwiftUI gives you access to SwiftUI-specific functionality . If you are writing UI Views you need to import SwiftUI.

  • Line 10 defines a BoltHome structure that helps us build the HomeUI of your Bolt(Wallet functionalities) code.

  • Line 11 Defines an Environment Object boltVM in BoltHome structure for sharing data between many views in your app.@EnvironmentObject property wrapper ensures views automatically stay updated when that data changes.

  • Line 12 defines a private variable sortOrder with @State wrapper to hold state without losing it. SwiftUI can destroy and recreate our struct whenever needed. sortOrder is storing state Transaction.creationDate via KeyPathComparator that uses a sort comparator to provide the comparison of Transaction Creation values at a key path.

  • Line 13 defines the variable bodyfor holding the main view of BoltHome screen.

  • Line 14 utilizes GeometryReader from SwiftUI library imported above to calculate screen position and size information .

  • Line 15 to 42 builds app layouts with stack views. Individually, HStack positions views in a horizontal line, VStack positions them in a vertical line, and ZStack overlays views on top of one another.

    • According to code, ZStack has its views on bottom with VStack defined inside it. View positioned vertically also includes available balance listed retrieved via AvailableBalanceBlock() method(Line 18).

    • WalletActionStack method is called at line 20 to provide relative sizes using GeometryReader for vertically positioned items.

    • Views for WalletApp that has to be positioned horizontally (HStack )

    • Line 28 bolds the whole text inside the HStack.

    • Line 29 defines a scrollable view within the scrollable content region .

    • Scroll indicators are set to false by default.

    • Line 30 defines what the scrollable view should contain which is for each sorted transaction by creation date list them as rows.

  • Line 43 to 53 defines the layout for the main View defined at Line 13

    • Padding for the main view is set to 20 pexels.

    • Environment Object boltVm is passed which holds the BoltViewModel instance.

    • Navigation Title which is set at top of app windows is named (Bolt)

    • App Title should be displayed in large texts.

    • .onAppear() adds an action(which is getting transaction details) before the main view appears.

    • Present an Alert for the user to receive ZCN

    • Print the Wallet Address by fetching the WalletClientID form Utils.swift

    • Present an alert for Sending ZCN and error(in case not able to send ZCN)

  • Line 57 to 67 defines a function for setting view of TransactionRow . The @ViewBuilder attribute is used with the function to create child views for a under the parent view which is BoltHome defined at line 10. The layout for transaction rows are as follows in HStack(Horizontally Stacked Views) : TransactionHash : Transaction Date Formatted

  • Line 69 to 76 defines variable sendAlert for holding alerts with the @ViewBuilder attribute to create child views.

    a) "some View” in means that the body will always be implementing the View protocol, but the implementation type does not need to be known by the caller.

    b) Text field for Wallet clientID and amount is specified and the button is implemented for executing send ZCN token transactions.

  • Line 78 defines variable recieveAlert for a child view that defines a button for copying the WalletClientID.

  • Line 84 to 95 defines a Previewprovider type that produces view for the declared View structures .

    a) Here we will pass our BoltHome structure defined at line 10 to be available as a View preview.(Line 92)

    b) At line 93 BoltHome is passed with an environment object boltVM for sharing data between many views in your app.

    c)boltVM is assigned a created BoltViewModel instance at line 87

    d)At line 88 boltVM populates the Transactions Array with transaction details accessible under BoltViewModel.swift.

Bolt/App/TransactionDetails.swift
//
//  TransactionDetails.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 07/01/23.
//
import SwiftUI

struct TransactionDetails: View {
    var transaction: Transaction
    var body: some View {
        List {
            Section("Signatur and Hashes") {
                ListRow(title: "Transaction Hash:", value: transaction.hash)
                ListRow(title: "Block Hash:", value: transaction.blockHash)
                ListRow(title: "Output Hash:", value: transaction.outputHash)
                ListRow(title: "Client ID:", value: transaction.clientID)
                ListRow(title: "To Client ID:", value: transaction.toClientID)
                ListRow(title: "Signature:", value: transaction.signature)
            }
            
            Section("Amount Details") {
                ListRow(title: "Status:", value: transaction.status.stringValue)
                ListRow(title: "Value:", value: transaction.value?.stringValue)
                ListRow(title: "Fee:", value: transaction.fee?.stringValue)
                ListRow(title: "Date:", value: transaction.fomattedDate)
            }
            
            Section("Explorer") {
                Link(destination: URL(string: "https://staging-atlus-beta.testnet-0chain.net/transaction-details/\(transaction.hash)")!,label: { Text("View On Explorer").foregroundColor(.teal) })
            }
        }
            .navigationTitle(Text("Transaction Details"))
    }
    
    @ViewBuilder func DictionarySection(title:String,value: String?) -> some View {
        let dict = value?.json ?? [:]
        Section(title) {
            ForEach(Array(dict.keys).sorted(),id:\.self) { key in
                ListRow(title: key, value: String(describing: dict[key]))
            }
        }
    }
}

struct TransactionDetails_Previews: PreviewProvider {
    static var previews: some View {
        TransactionDetails(transaction: Transaction(createdAt: "", updatedAt: "", devaredAt: "", hash: "dgjydgjydgydgyjdgyddgjydgjydgydgyjdgyddgjydgjydgydgyjdgyd", blockHash: "duhdhdigdugdi", round: 2, version: "", clientID: "83738383763", toClientID: "397387387383", transactionData: "", value: 4, signature: "duhdujhdudh", creationDate: 2344, fee: 888, nonce: 88, transactionType: 2, transactionOutput: "djhdjhdj", outputHash: "djhdhidi", status: 2))
    }
}

struct ListRow: View {
    var title:String
    var value: String?
    @State var opened: Bool = false
    
    var body: some View {
        HStack {
            Text(title)
            Text(value ?? "~")
                .lineLimit(10)
        }
        .onTapGesture {
            withAnimation {
                self.opened.toggle()
            }
        }
    }
}

Describing Code:

  • Line 8 import SwiftUI gives you access to iOS UI-specific functionality . If you are writing SwiftUi View you need to import SwiftUI.

  • Line 10 defines a structure TransactionDetails which contains various properties and fields for creating view for transaction details.

  • Line 11 defines a transaction variable that points to Transaction Structure

  • Line 12 defines the variable bodyfor holding the main view of Transaction Details

  • Line 13 defines a List for displaying a collection of data

  • Inside that at line 14 ,to add a section around some cells, a Section statement with title is added.

  • Line 15 to 20 will create rows under the list that holds Transaction data.

  • Line 23 to 28 again adds a section under the list with the rows that holds Transaction Amount Details (Status.Value,Fee,Date)

  • Line 30 to 33 adds another section in the list for Explorer under which a redirecting link to all-in -one network dashboard is listed to view Transaction details in more detail.

  • Line 34 defines the navigation title for TransactionDetails structure.

  • Line 37 to 45 defines a function DictionarySection implementing a dictionary for holding transaction data that has to be populated in the list rows(Line 40 and 41) .

  • The @ViewBuilder attribute is used with the function to create child views under the parent view which is TransactionDetails struct defined at Line 10.

  • Line 47 to 51 defines a PreviewProvider type that produces view previews for the declared TransactionDetails struct .

  • Line 53 to 70 defines a structure on how how transaction list in terms of rows should be created.

  • According to the structure two variables are defined -- a variable for List title is defined and an optional string variable value that might hold or not hold value depending u[on the case .

  • Line 56 defines a boolean variable holding the toggle state which is set to false by default

  • Line 58 defines how views should be stacked horizontally.

  • First ,title should be listed and then comes value variable horizontally to that.(Line 60 and 61 )

  • The maximum number of lines that the text can occupy in a view is 10(Line 62)

  • Line 64 utilizes .onTapGesture function which adds an action to perform when the view recognizes a tap gesture.

  • Line 65 utilizes withAnimation function which returns the view’s body result with the defined animation which is self.opened.toggle() .

  • self refers to the current object within the ListRow struct.

  • The toggle () function will switch the bool from true to false.

Bolt/Model/Balance.swift
//
//  Balance.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 29/12/22.
//

import Foundation
import Zcncore

struct Balance: Codable, Equatable {
    
    private var txn: String?
    private var round: Int?
    private var _balance: Int?
    private var error: String?
    
    internal init(txn: String? = nil, round: Int? = nil,balance _balance: Int? = nil, error: String? = nil) {
        self.txn = txn
        self.round = round
        self._balance = _balance
        self.error = error
    }
    
    enum CodingKeys: String, CodingKey {
        case txn
        case round
        case _balance = "balance"
        case error
    }
    
    var balance: Int {
        return _balance ?? 0
    }
    
    var balanceToken: Double {
        get { return balance.tokens }
        set { self._balance = Int(exactly: ZcncoreConvertToValue(newValue).doubleValue) }
    }
    
    var usd: String {
        let usd: Double = Utils.zcnUsdRate
        let amount: Double = balanceToken * usd
        let dollarString: String = "$ \(amount.rounded(toPlaces: 3))"
        return dollarString
    }
    
}

Describing Code:

  • Line 8 import Foundation is required to provide a base layer of functionality for apps and frameworks, including data storage and persistence, text processing, date and time calculations, sorting and filtering, and networking.

  • Line 9 import Zcncore is required to access Gosdk functions.

  • Line 11 defines a structure for how wallet balance should be retrieved. Codable means structure allows decoding data for custom types and encoding data to be saved or transferred.

  • Line 13 to 16 defines private variables in the struct for organizing balance details.

a)txn: holds transaction hash ​ b)round : holds block rounds information c) balance : holds wallet balance ​ d)error: hold balance error information

  • Line 18 to 22 internally initializes instance of a Balance structure .self refers to the current object within the Balance structure.

  • Line 25 to 30 defines constants or enums(not changable variable) for the Balance structure.

  • Line 32 to 34 defines balance function which will return wallet balance as integer.

  • Line 36 to 39 defines balanceToken function with a getter and setter methods.A getter method here allows access to private balance.tokens property, and where as setter method allows the private balance property to be set with a new value which is ZCN tokens to value of choice(Double) via ZcncoreConvertToValue function.

  • Line 41 to 48 defines functionusd for getting ZCN token balance in terms of USD

    a) A usd variable of type Double that gets value via Utils.zcnUsdRate function in Utils.swift.

    b) A amount variable of type Double that gets its value from balanceToken at line 43

    c) A dollarString variable that returns ZCN token value in terms of USD.

Bolt/Model/Transaction.swift
//
//  Transaction.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 29/12/22.
//

import Foundation

typealias Transactions = [Transaction]

struct Transaction: Codable, Identifiable, Hashable,Comparable {
    static func < (lhs: Transaction, rhs: Transaction) -> Bool {
        return rhs.creationDate < lhs.creationDate
    }
    
    /// <#Description#>
    /// - Parameters:
    ///   - id: <#id description#>
    ///   - createdAt: <#createdAt description#>
    ///   - updatedAt: <#updatedAt description#>
    ///   - devaredAt: <#devaredAt description#>
    ///   - hash: <#hash description#>
    ///   - blockHash: <#blockHash description#>
    ///   - round: <#round description#>
    ///   - version: <#version description#>
    ///   - clientID: <#clientID description#>
    ///   - toClientID: <#toClientID description#>
    ///   - transactionData: <#transactionData description#>
    ///   - value: <#value description#>
    ///   - signature: <#signature description#>
    ///   - creationDate: <#creationDate description#>
    ///   - fee: <#fee description#>
    ///   - nonce: <#nonce description#>
    ///   - transactionType: <#transactionType description#>
    ///   - transactionOutput: <#transactionOutput description#>
    ///   - outputHash: <#outputHash description#>
    ///   - status: <#status description#>
    internal init(id: Int? = nil, createdAt: String? = nil, updatedAt: String? = nil, devaredAt: String? = nil, hash: String, blockHash: String = "", round: Int? = nil, version: String? = nil, clientID: String? = nil, toClientID: String? = nil, transactionData: String? = nil, value: Int? = nil, signature: String? = nil, creationDate: Double, fee: Int? = nil, nonce: Int? = nil, transactionType: Int? = nil, transactionOutput: String? = nil, outputHash: String? = nil, status: Int) {
        self.id = id
        self.createdAt = createdAt
        self.updatedAt = updatedAt
        self.devaredAt = devaredAt
        self.hash = hash
        self.blockHash = blockHash
        self.round = round
        self.version = version
        self.clientID = clientID
        self.toClientID = toClientID
        self.transactionData = transactionData
        self.value = value
        self.signature = signature
        self.creationDate = creationDate
        self.fee = fee
        self.nonce = nonce
        self.transactionType = transactionType
        self.transactionOutput = transactionOutput
        self.outputHash = outputHash
        self.status = status
    }
    
    var id: Int?
    var createdAt, updatedAt: String?
    var devaredAt: String?
    var hash, blockHash: String
    var round: Int?
    var version, clientID, toClientID, transactionData: String?
    var value: Int?
    var signature: String?
    var creationDate: Double
    var fee, nonce, transactionType: Int?
    var transactionOutput, outputHash: String?
    var status: Int

    enum CodingKeys: String, CodingKey {
        case id = "ID"
        case createdAt = "CreatedAt"
        case updatedAt = "UpdatedAt"
        case devaredAt = "DevaredAt"
        case hash
        case blockHash = "block_hash"
        case round, version
        case clientID = "client_id"
        case toClientID = "to_client_id"
        case transactionData = "transaction_data"
        case value, signature
        case creationDate = "creation_date"
        case fee, nonce
        case transactionType = "transaction_type"
        case transactionOutput = "transaction_output"
        case outputHash = "output_hash"
        case status
    }
    
    var fomattedDate: String {
        return Date(timeIntervalSince1970: self.creationDate/1e9).formatted()
    }
}

Descriibing Code :

  • Line 8 import Foundation is required to provide a base layer of functionality for apps and frameworks, including data storage and persistence, text processing, date and time calculations, sorting and filtering, and networking.

  • Line 12 defines a Transaction structure for app transactions.

  • Line 39 to 60 internally initializes instance of a Balance structure with variablesvariables .self refers to the current instance within the Balance structure.

  • Line 62 to 73 defines optional variables( can hold None and Some) for holding various transaction details.

         var id: Int?   ///Transaction ID
         var createdAt, updatedAt: String?   //
         var devaredAt: String?
         var hash, blockHash: String    ///Transaction Hash
         var round: Int?     ////Current Round Chain
         var version, clientID, toClientID, transactionData: String?   ///
         var value: Int?
         var signature: String?    ///Wallet Signature 
         var creationDate: Double  
         var fee, nonce, transactionType: Int?
         var transactionOutput, outputHash: String?
         var status: Int  //Transaction Status
  • Line 95 to 97 defines function for returning formattedDate that will be required in transaction information. self refers to the current instance within the Balance structure.

Bolt/Model/Wallet.swift
//
//  Wallet.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 28/12/22.
//
import Foundation

struct Wallet: Codable {
    let client_id: String
    var client_key: String
    var keys: [Keys]
    var mnemonics: String
    let version: String
    
    func debugDescription() -> String {
        return "\n----------Wallet----------\nclient_id: \(client_id)\nclient_key: \(client_key)\npublic_key: \(keys[0].public_key)\nprivate_key: \(keys[0].private_key)\nmnemonics: \(mnemonics)\nversion: \(version)\n--------------------------"
    }
}

struct Keys: Codable {
    let public_key: String
    let private_key: String
}

Describing Code:

  • Line 8 import Foundation is required to provide a base layer of functionality for apps and frameworks, including data storage and persistence, text processing, date and time calculations, sorting and filtering, and networking.

  • Line 10 defines a Wallet structure for getting wallet information in the app.

  • Line 11 to 15 defines variables in the wallet structure for holding wallet details.

a) client_id: holds wallet client_id ​ b) client_key : holds wallet client_key ​ c) keys : holds wallet public and private keys ​ d) mnemonics: holds wallet mnemonics ​ e) version : holds wallet version.

  • Line 17 to 20 defines a function for returning wallet information .

  • Line 22 to 25 defines Key structure for wallet public and private keys.

Bolt/View/AvailableBalanceBlock.swift
//
//  AvailableBalanceBlock.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 29/12/22.
//

import SwiftUI

struct AvailableBalanceBlock: View {
    @EnvironmentObject var boltVM: BoltViewModel
    @AppStorage(Utils.UserDefaultsKey.balance.rawValue) var balance: Int = 0

    var body: some View {
        VStack(alignment:.leading,spacing: 5) {
            
            Text("Available Balance")
                .font(.system(size: 14, weight: .regular))
            
            HStack(alignment:.bottom,spacing:0) {
                Text("\(balance.tokens.rounded(toPlaces: 3))")
                    .font(.system(size: 36, weight: .bold))
                Text(" ZCN")
                    .font(.system(size: 14, weight: .regular))
                    .padding(.bottom, 8)
            }
            .padding(.top,-10)
            
            HStack {
                Text("Total Balance")
                    .font(.system(size: 16, weight: .regular))
                Text("$ \(balance.usd)")
                    .font(.system(size: 16, weight: .bold))
                
            }
            
            Text("1 ZCN ≈ $\(Utils.zcnUsdRate)")
                .foregroundColor(.secondary)
        }
    }
}

struct AvailableBalanceBlock_Previews: PreviewProvider {
    static var previews: some View {
        AvailableBalanceBlock()
            .environmentObject(BoltViewModel())
            .padding(20)
            .previewLayout(.sizeThatFits)
    }
}

Describing Code:

  • Line 8 import SwiftUI gives you access to SwiftUI-specific functionality . If you are writing UI Views in your iOS app you need to import SwiftUI.

  • Line 10 defines a AvailableBalanceBlock structure that helps us build the AvailableBalanceBlock in the Bolt(Wallet) App. <insert image here>

  • Line 11 defines a BoltViewModel variable boltVM in for sharing data between various Bolt app views in your app(here ).@EnvironmentObject property wrapper ensures views automatically stay updated when that data changes.

  • Line 12 defines a integer variable balance with @AppStorage wrapper for reading values from UserDefaults, The wrapper effectively watches a balance in UserDefaults, and will refresh your UI if that key changes.

  • Line 14 defines the variable bodyfor holding the AvailableBalanceBlock view on the app screen.

  • Line 15 to 42 builds app layouts with stack views. Individually, HStack positions views in a horizontal line, VStack positions them in a vertical line. a) Vertically positioned views (VStack ) are as follows: "Available Balance(text)" with spacing then amount of tokens fetched and rounded off from method in Balance.swift horizontally stacked. (Line 15 to 27)

b) More Horizontally positioned views (HStack) are as follows : Total Balance with token balance in ZCN then its ZCN equivalent in USD. (Line 29 to 41)

  • Line 43 to 50 defines a Previewprovider type that produces view for the declared AvailableBalanceBlock structure. a) Here we will pass our AvailableBalanceBlock structure defined at line 10 to be available as a View preview.(Line 45) b) At line 46 BoltViewModel is passed with an environment object for sharing data and access methods between views in your app. c) 20 pixels for padding and spacing between previews.(Line47) d)Set size constrains and layout for the preview according to the amount of space view requires(Line 48)

Bolt/View/WalletActionStack.swift

//  WalletActionStack.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 29/12/22.
//

import SwiftUI

struct WalletActionStack: View {
    @EnvironmentObject var boltVM: BoltViewModel
    @Environment(\.colorScheme) var colorScheme
    var width: CGFloat
    
    var body: some View {
        HStack(spacing:0) {
            ForEach(WalletActionType.allCases,id:\.self) { action in
                WalletActionButton(width: width, action: boltVM.walletAction, button: action)

            }
        }
        .frame(height:width/4)
        .background(Color.tertiarySystemBackground)
        .cornerRadius(12)
        .shadow(color: .init(white: colorScheme == .dark ? 0.05 : 0.75), radius: 75, x: 0, y: 0)
        .padding(.bottom,10)
    }
}

struct WalletActionButton: View {
    var width: CGFloat
    var action: (WalletActionType)->()
    var button: WalletActionType
    
    init(width: CGFloat, action: @escaping (WalletActionType) -> (), button: WalletActionType) {
        self.width = width
        self.action = action
        self.button = button
    }
    
    var body: some View {
        VStack(spacing:15) {
            Image(button.image)
                .resizable()
                .aspectRatio(1, contentMode: .fit)
                .frame(width: width/13)
            Text(button.title)
                .font(.system(size: 13, weight: .semibold))
                .foregroundColor(Color(uiColor: UIColor.label))
}
        .frame(width: width/3)
        .onTapGesture{ self.action(button) }
    }
}

enum WalletActionType: CaseIterable {
    case send
    case receive
    case faucet
    
    var title: String {
        switch self {
        case .send: return "Send"
        case .receive: return "Receive"
        case .faucet: return "Faucet"
        }
    }
    
    var image: String {
        switch self {
        case .send: return "send"
        case .receive: return "receive"
        case .faucet: return "faucet"
        }
    }
}

struct WalletActionStack_Previews: PreviewProvider {
    static var previews: some View {
        WalletActionStack(width: 345)
            .padding(50)
            .background()
            .environmentObject(BoltViewModel())
            .previewLayout(.sizeThatFits)
    }
}

Describing Code:

  • Line 8 import SwiftUI gives you access to SwiftUI-specific functionality . If you are writing UI Views in your iOS app you need to import SwiftUI.

  • Line 10 defines a WalletActionStack structure that helps us define functionalities for the wallet (sending or receiving wallet tokens).

  • Line 11 defines a BoltViewModel variable boltVM in for sharing data between main app and wallet stack view in your app(here ).@EnvironmentObject property wrapper ensures views automatically stay updated when that data changes.

  • Line 12 creates a property that reads the color scheme of the current view using the key path of the colorScheme property.

  • Line 13 defines a variable width pointing to CGFloat for floating-point scalar values in Core Graphics and related frameworks.

  • Line 15 to 28 builds app layouts with stack views. Individually, HStack positions views in a horizontal line,

    a) Horizontally Stacked views (HStack ) are as follows: For each Wallet Action these are the following layouts:

    Frame: Positions view within an invisible frame with the specified size. Background: System background uses the default background color based on light/dark mode. Corner Radius: Round the corners of a view by 12 points. Shadow: Adds a dark shadow to this view. Padding: Adds horizontal padding 10pt to specific edges of this view.

  • Line 30 implements a WalletActionButton structure which define properties for implementing buttons for wallet actions.

  • Line 31 defines a variable width pointing to CGFloat for floating-point scalar values in Core Graphics and related frameworks.

  • A action variable which can switch between different WalletActionType.

  • A button variable which can holds buttons for different WalletActionType.

  • Line 35 to 39 initializes WalletActionButton instance with its properties.

  • Line 41 to 53 defines view layout for the WalletActionButton

    a) VStack positions views in a vertical line with the following properties. (Line 42)

    b) A button image with .resizable() mode: Sets the mode by which SwiftUI resizes an image to fit its space.(Line 43 and 44)

    c) .frame(width:width/13): Positions this view within an invisible frame with the specified size(width of window/13)(Line 46).

    d) A button title with text and font size and weight specified(Line 47 and 48)

    e) Total frame width for the complete button layout with title will be 1/3 of actual size .

f) Line 51 utilizes .onTapGesture function which adds an action to perform when the view recognizes a button click.

  • Line 55 to to 75 define Swift enumerations to store WalletActionType values which are send(send tokens),receive(receive tokens) and faucet(get tokens into wallet)

  • Line 60 to 66 uses switch control statements for flow of WalletActionTypes titles.

    The switch statement evaluates an expression inside paranthesis which is self(WalletActionType Instance)(Line 61) . If the result of the expression is equal to a particular case that is (send,.receive,and .faucet) then statements for that particular case are executed.

  • Line 68 to 75 uses control statements for flow of WalletActionTypes images.

    The switch statement evaluates an expression inside paranthesis which is self(WalletActionType Instance)(Line 69) . If the result of the expression is equal to a particular case that is (.send,.receive,and .faucet) then statements for that particular case are executed.

  • Line 77 to 83 defines a PreviewProvider type that produces view for the declared WalletActionStack structure.

    a) Here we will pass our WalletActionStack instance defined as a structure at line 10 to be available as a View preview.(Line 45)

    b) At line 80 BoltViewModel is passed as an environment object for sharing data and access methods between views in your app.

    c) Set size constrains and layout for the preview according to the amount of space view requires(Line 81)

Bolt/ViewModel/BoltViewModel.swift
//
//  BoltViewModel.swift
//  ZusExample
//
//  Created by Aaryan Kothari on 28/12/22.
//

import Foundation
import Zcncore
import Combine
import SwiftUI

class BoltViewModel:NSObject, ObservableObject {
    
    @Published var balance: Double = 0.0
    @Published var balanceUSD: String = "$ 0.00"

    @Published var presentReceiveView: Bool = false
    @Published var presentErrorAlert: Bool = false
    @Published var presentSendView: Bool = false

    @Published var alertMessage: String = ""
    @Published var clientID: String = ""
    @Published var amount: String = ""

    @Published var transactions: Transactions = []
    
    @Published var presentPopup: Bool = false
    @Published var popup = ZCNToast.ZCNToastType.success("YES")
    
    var cancellable = Set<AnyCancellable>()
    
    override init() {
        super.init()
        Timer.publish(every: 30, on: RunLoop.main, in: .default)
            .autoconnect()
            .map { _ in }
            .sink(receiveValue: getBalance)
            .store(in: &cancellable)
        
        if let balance = Utils.get(key: .balance) as? Int {
            self.balance = balance.tokens
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
        self.cancellable.removeAll()
    }
    
    func getBalance() {
        var error: NSError? = nil
        ZcncoreGetBalance(self, &error)
        if let error = error { print(error.localizedDescription) }
    }
    
    func walletAction(_ action: WalletActionType) {
        switch action {
        case .send:
            self.presentSendView = true
        case .receive:
            self.presentReceiveView = true
        case .faucet:
            self.receiveFaucet()
        }
    }
    
    func receiveFaucet() {
        self.presentSendView = false
        DispatchQueue.global().async {
            var error: NSError?
            
            do {
                
                DispatchQueue.main.async {
                    self.popup = .progress("Recieving ZCN from faucet")
                    self.presentPopup = true
                }
                
                let txObj =  ZcncoreNewTransaction(self,"0",0,&error)
                
                if let error = error { throw error }

                try txObj?.executeSmartContract("6dba10422e368813802877a85039d3985d96760ed844092319743fb3a76712d3",
                                               methodName: "pour",
                                               input: "{}",
                                               val: "10000000000")
            } catch let error {
                self.onTransactionFailed(error: error.localizedDescription)
            }
        }
    }
    
    func sendZCN() {
        DispatchQueue.global(qos: .default).async {
            do {
                guard let amount = Double(self.amount) else {
                    self.onTransactionFailed(error: "invalid amount")
                    return
                }
                
                guard self.clientID.isValidAddress else {
                    self.onTransactionFailed(error: "invalid address")
                    return
                }
                
                guard !amount.isZero else {
                    self.onTransactionFailed(error: "amount cannot be zero")
                    return
                }
                
                guard amount <= self.balance else {
                    self.onTransactionFailed(error: "amount cannot be greater than balance")
                    return
                }
                
                guard Utils.wallet?.client_id != self.clientID else {
                    self.onTransactionFailed(error: "cannot send to own wallet")
                    return
                }
                
                DispatchQueue.main.async {
                    self.popup = .progress("Sending ZCN")
                    self.presentPopup = true
                }
                
                var error: NSError? = nil
                let txObj =  ZcncoreNewTransaction(self,"0",0,&error)
                
                if let error = error { throw error }
                
                try txObj?.send(self.clientID, val: ZcncoreConvertToValue(amount), desc: "")
                
                DispatchQueue.main.async {
                    self.clientID = ""
                    self.amount = ""
                }
            } catch let error {
                self.onTransactionFailed(error: error.localizedDescription)
            }
        }
    }
    
    func copyClientID() {
        PasteBoard.general.setString(Utils.wallet?.client_key)
    }
    
    func getTransactions() {
        DispatchQueue.global().async {
            var error: NSError? = nil
            let clientId = Utils.wallet?.client_id
            
            ZcncoreGetTransactions(clientId, nil, nil, "desc", 20, 0, self , &error)
            ZcncoreGetTransactions(nil, clientId, nil, "desc", 20, 0, self , &error)
            
            if let error = error { print(error.localizedDescription) }
        }
    }
    
    func onTransactionComplete(t: ZcncoreTransaction) {
        DispatchQueue.main.async {
            self.popup = .success("Success")
            self.presentPopup = true
        }
    }
    
    func onVerifyComplete(t: ZcncoreTransaction) {
       
    }
    
    func onTransactionFailed(error: String) {
        DispatchQueue.main.async {
            self.popup = .error(error)
            self.presentPopup = true
        }
    }
    
}

extension BoltViewModel: ZcncoreGetBalanceCallbackProtocol {
    func onBalanceAvailable(_ status: Int, value: Int64, info: String?) {
        guard let response = info,
              let data = response.data(using: .utf8),
              let balance = try? JSONDecoder().decode(Balance.self, from: data) else {
                  return
              }
        Utils.set(value, for: .balance)
        DispatchQueue.main.async {
            self.balance = balance.balanceToken
            self.balanceUSD = balance.usd
        }
    }
}

extension BoltViewModel: ZcncoreTransactionCallbackProtocol {
    func onAuthComplete(_ t: ZcncoreTransaction?, status: Int) { }
    
    func onTransactionComplete(_ t: ZcncoreTransaction?, status: Int) {
        
        DispatchQueue.main.async {
            let transaction = Transaction(hash: t?.getTransactionHash() ?? "", creationDate: Date().timeIntervalSince1970 * 1e9, status: status == 0 ? 1 : 2)
            self.transactions.append(transaction)
            self.objectWillChange.send()
        }
        
        guard status == ZcncoreStatusSuccess,
              let txObj = t else {
            self.onTransactionFailed(error: t?.getTransactionError() ?? "error: \(status)")
            return
        }
        try? txObj.verify()
        
        self.onTransactionComplete(t: txObj)
    }
    
    func onVerifyComplete(_ t: ZcncoreTransaction?, status: Int) {
        self.getBalance()
    }
}

extension BoltViewModel: ZcncoreGetInfoCallbackProtocol {
    func onInfoAvailable(_ op: Int, status: Int, info: String?, err: String?) {
        guard status == ZcncoreStatusSuccess,
              let response = info,
              let data = response.data(using: .utf8,allowLossyConversion: true) else {
            print(err ?? "onInfoAvailable Error")
            return
        }
        
        do {
            if op == ZcncoreOpStorageSCGetTransactions {
                let txns = try JSONDecoder().decode(Transactions.self, from: data)
                var transactions = self.transactions
                transactions.append(contentsOf: txns)
                DispatchQueue.main.async {
                    self.transactions = Array(Set(transactions))
                }
            }
        } catch let error {
            print(error)
        }
    }
}

Describing Code:

  • Line 8 to 11 import libraries Foundation is required to provide a base layer of functionality for apps and frameworks, including data storage and persistence, text processing, date and time calculations, sorting and filtering, and networking.

    Zcncore is required to access Gosdk functions.

    SwiftUI gives you access to SwiftUI-specific functionality . If you are writing UI Views in your iOS app you need to import SwiftUI.

    Combine gives handling of asynchronous events.

  • Line 13 BoltViewModel class utilizes the zcncore libraries and provide the following functionalities for Bolt UI: a) getBalance(): Retrieve Wallet Balance(Line 51 to 55) b) recieveFaucet(): Receive tokens in wallet (Line 68 to 92) c) sendZCN() : Send ZCN Tokens (Line 94 to 142) d) copyClientID() : Copy Wallet ClientID(Line 144 to 146) e) getTransactions(): Get Wallet Transactions(Line 148 to 158) f) onTransactionComplete(): A function handling responses for successful transaction. (Line 160 to 165) g) onTransactionFailed(): A function handling responses for failed transactions. (Line 171 to 178)

  • ZcncoreGetBalanceCallbackProtocol() extends new functionality to the BoltViewModel structure with the onBalanceAvailable method which return ZCN token balance in callback.(Line 180 to 193)

  • ZcncoreTransactionCallbackProtocol() extends new functionality to the BoltViewModel structure with onTransactionComplete method which is called back when transaction is complete.(Line 198 to 214)

  • ZcncoreGetInfoCallbackProtocol() extends new functionality to the BoltviewModel structure with onInfoAvailable method which is called back for transaction information. (Line 221 to 243)

Last updated