Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Intermediate · 30 minutes

Build Algorand Android Smart Contract using Kotlin

Kotlin is a modern statically typed programming language used by over 60% of professional Android developers that helps boost productivity, developer satisfaction, and code safety. Kotlin can also be used to create multi-platform (Android/IOS) applications. But Macbook will be required to create Kotlin Multiplatform Mobile (KMM). More info here .

Recently most Android development is done using Kotlin. And as the time of writing this tutorial there is no Kotlin guide for integrating the Algorand SDK & REST-API.

This tutorial is for Android developers using Kotlin who want to integrate the Algorand on their application. For this tutorial we will be using the Algorand Java-SDK and the Purestake REST API as not all queries are available on the REST-API at the time of this tutorial.

I will be following Kotlin/Android development best practice here using Model View ViewModel (MVVM), more info on MVVM can be found here and Couroutines for handling background task, Databinding, Timber for logging and Hilt for dependency injection. Only code snippet relating to actual interraction with the Algorand service will be shown here. The rest of the code can be found on the github repo .

Figure 0-1

EditorImages/2021/08/02 00:45/MacBook_-_1_1.png

This tutorial will cover

  • Account creation

  • Funding accounts

  • Transferring funds

  • Getting account transactions

  • Stateful and Stateless smart contract

Requirements

  • Android studio setup

  • Familiarity with the Java and Kotlin programming language and its use within the Android Environment.

  • Basic understanding of some blockchain terminologies.

Steps

1.Workspace setup and configuration in android studio

To get started, your android studio should be up and running. Then add all necessary dependencies to your app/build.gradle file. To start using the Algorand SDK here are the dependencies you would need.

implementation 'com.algorand:algosdk:1.7.0'

To run the completed code, the Minimum version of Android Studio 4.1 or newer. And Java SDK is 1.8.0 will be required.

Note: Constants.kt hold the constants for the app.

2.Account creation and Generate Algod Pair

Account Creation
In order to send a transaction, you first need an account on Algorand. Create an account by generating an Algorand public/private key pair and then funding the public address with Algos on your chosen network.

To get the Algorand REST- API you will need to register on purestake or another API services such as AlgoExplorer.io or your own node with an ip address (not localhost)

If you are using the Algorand Purestake API, Once you are done with the registration, you will be provided with your unique API-KEY for making API queries.

Generate Algod pair
When creating a new account you will need to generate a private key and Mnemonic. To do that with the Algorand Java SDK here is the code required;

AccountRepository

suspend fun generateAlgodPair(): Account

AccountRepositoryImpl

override suspend fun generateAlgodPair() : Account {
        return Account()
    }

AccountViewmodel

    fun generateAlgodPair() : LiveData<Account> {
        viewModelScope.launch {
            _algodPair.value = repository.generateAlgodPair()
        }
        return algodPair
    }

MainActivity

private fun createAccount(){
       viewModel.generateAlgodPair().observe(this, {
            binding.publicKeyAddress.text = it.address.toString()
            binding.newPassphrase.text = it.toMnemonic()
            Timber.d("address ${it.address}")
            Timber.d("phrase ${it.toMnemonic()}")
        })
    }

3.Import an existing account

To import an existing Algorand Account, you need to get the Mnemonic from where ever you saved it from the one you generated previously when creating a new account.It is always advisable to save your Mnemonic properly and not to be shared with anyone.If you loose your Mnemonic you will lost access to your account and most likely loose your assets in that account.

Here is what the code looks like

AccountRepository

    suspend fun recoverAccount(passPhrase : String): Account

AccountRepositoryImpl

 override suspend fun recoverAccount(passPhrase : String): Account {
        val account = Account(passPhrase)
        Timber.d("Account $account")
        return  account
    }

AccountViewmodel

fun recoverAccount(passsPhrase : String) : LiveData<Account>{
        viewModelScope.launch {
           _algodPair.value = repository.recoverAccount(passsPhrase)
        }
        return algodPair
    }

MainActivity

private fun recoverAccount(){
        viewModel.recoverAccount(SRC_MNEMONIC).observe(this, Observer {
            binding.recoveryPublicKeyAddress.text = it.address.toString()
            binding.recoveryPassphrase.text = it.toMnemonic()
        })
    }

4.Funding an account

To fund your Algord test account you can use the Algorand Dispenser

Here is the code to fund wallet from our example

Dashboard

private fun fundAccount() {
        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Constants.FUND_ACCOUNT))
        try {
            if (!Constants.FUND_ACCOUNT.startsWith("http://") && !Constants.FUND_ACCOUNT.startsWith(
                    "https://"
                )
            )
                Constants.FUND_ACCOUNT = "http://" + Constants.FUND_ACCOUNT;
            startActivity(browserIntent)
        } catch (e: Exception) {
            Timber.d("Host not available ${e.message}")
        }
    }

5.Checking account balance

To check account balance we will need to get the account info. To do this we can use either the Algorand SDK or the Purestake API. For this example we will be making use of the PureStake API.

WalletAccount Model

class WalletAccount(
    val account: Account,
    @SerializedName("current-round")
    val currentRound: Long
)


data class Account(
    val address: String,
    val amount: Long,

    @SerializedName("amount-without-pending-rewards")
    val amountWithoutPendingRewards: Long,

    @SerializedName("created-at-round")
    val createdAtRound: Long,

    val deleted: Boolean,

    @SerializedName("pending-rewards")
    val pendingRewards: Long,

    @SerializedName("reward-base")
    val rewardBase: Long,

    val rewards: Long,
    val round: Long,

    @SerializedName("sig-type")
    val sigType: String,

    val status: String
)

AlgorandRESTService - Purestake API endpoint

@Headers(HEADER_VALUE)
    @GET("v2/accounts/{account-id}")
    suspend fun getAccountByAddress(@Path("account-id") account_id: String?): Response<WalletAccount>

AccountRepository

    suspend fun getAccount(address : String) : WalletAccount?

AccountRepositoryImpl

override suspend fun getAccount(address: String): WalletAccount? {
        val response = apiService.service.getAccountByAddress(address)
        try {
            val accountInfo = response.body()
            if (response.isSuccessful) {
                val account = response.body()?.account
                Timber.d("acctResponse $response")
                Timber.d("address is $address and account is $account")
                Timber.d("account info ${accountInfo.toString()}")
            } else {
                Timber.d("Error ${response.errorBody()}")

            }
        }catch (e : Exception){
            Timber.d(e)
        }
        return response.body()
    }

AccountViewmodel

fun getAccount(address: String) : LiveData<WalletAccount>{
             viewModelScope.launch {
            try{
                _account.value  = repository.getAccount(address)
            }catch(e: Exception){e.message}
        }
        return account
    }

Dashboard

 private fun getWalletBalance() {
        viewModel.getAccount(CREATOR_ADDRESS)
            .observe(this, {
                try {
                    binding.balance.text = it.account.amount.toString()
                    Timber.d("amount ${it.account.amount}")
                } catch (e: Exception) {
                    e.message
                }
            })
    }

6.Sending a payments

To transfer fund using the Algorand SDK, first you need to setup your AlgodClient provide the appropriate values. This is the code required to get that done.

TransactionRepository

    suspend fun transferFund(amount: Long, receiverAddress: String)
    suspend fun submitTransaction(signedTxn: SignedTransaction): String
    suspend fun waitForConfirmation(txID: String)

TransactionRepositoryImpl

        private val client: AlgodClient = AlgodClient(
            Constants.ALGOD_API_ADDR,
            Constants.ALGOD_PORT,
            Constants.ALGOD_API_TOKEN,
        )
    var headers = arrayOf("X-API-Key")
    var values = arrayOf(ALGOD_API_TOKEN_KEY)

    override suspend fun transferFund(amount: Long, receiverAddress: String) {
        val passPhrase = PASSPHRASE
        val myAccount = Account(passPhrase)

        <!-- Construct the transaction -->
        val RECEIVER = RECIEVER
        val sender = SENDER
        withContext(Dispatchers.IO) {
            val resp: Response<TransactionParametersResponse> = client.TransactionParams().execute(
                headers,
                values
            )
            try {
                if (!resp.isSuccessful) {
                    Timber.d("message ${resp.message()}")
                    throw Exception(resp.message())
                }
                val params: TransactionParametersResponse = resp.body()
                    ?: throw Exception("Params retrieval error")
                val txn: Transaction = Transaction.PaymentTransactionBuilder()
                    .sender(sender)
                    .amount(amount)
                    .receiver(Address(receiverAddress))
                    .suggestedParams(params)
                    .build()

                //Sign the transaction
                val signedTxn: SignedTransaction = myAccount.signTransaction(txn)
                Timber.d("Signed transaction with txid:: \" + ${signedTxn.transactionID}")
                submitTransaction(signedTxn)
            } catch (e: Exception) {
                Timber.d("Error ${e.message}")
            }

        }
    }

    /**
     * utility function to wait on a transaction to be confirmed
     * the timeout parameter indicates how many rounds do you wish to check pending transactions for
     */
    override suspend fun waitForConfirmation(
        myclient: AlgodClient?,
        txID: String?,
        timeout: Int
    ): PendingTransactionResponse? {
        require(!(myclient == null || txID == null || timeout < 0)) { "Bad arguments for waitForConfirmation." }
        var resp = myclient.GetStatus().execute(headers, values)
        if (!resp.isSuccessful) {
            throw java.lang.Exception(resp.message())
        }
        val nodeStatusResponse = resp.body()
        val startRound = nodeStatusResponse.lastRound + 1
        var currentRound = startRound
        while (currentRound < startRound + timeout) {
            // Check the pending transactions
            val resp2 = myclient.PendingTransactionInformation(txID).execute(headers, values)
            if (resp2.isSuccessful) {
                val pendingInfo = resp2.body()
                if (pendingInfo != null) {
                    if (pendingInfo.confirmedRound != null && pendingInfo.confirmedRound > 0) {
                        // Got the completed Transaction
                        return pendingInfo
                    }
                    if (pendingInfo.poolError != null && pendingInfo.poolError.length > 0) {
                        // If there was a pool error, then the transaction has been rejected!
                        throw java.lang.Exception("The transaction has been rejected with a pool error: " + pendingInfo.poolError)
                    }
                }
            }
            resp = myclient.WaitForBlock(currentRound).execute(headers, values)
            if (!resp.isSuccessful) {
                throw java.lang.Exception(resp.message())
            }
            currentRound++
        }
        throw java.lang.Exception("Transaction not confirmed after $timeout rounds!")
    }

    override suspend fun submitTransaction(signedTxn: SignedTransaction): String {
        val txHeaders: Array<String> = ArrayUtils.add(headers, "Content-Type")
        val txValues: Array<String> = ArrayUtils.add(values, "application/x-binary")
        var id = ""
        try {
            // Submit the transaction to the network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val rawtxresponse = client.RawTransaction().rawtxn(encodedTxBytes).execute(
                txHeaders,
                txValues
            )
            if (!rawtxresponse.isSuccessful) {
                Timber.d("raw ${rawtxresponse.message()}")
                throw Exception(rawtxresponse.message())
            } else {
                 id = rawtxresponse.body().txId
                Timber.d("Successfully sent tx with ID: $id")
                waitForConfirmation(id)
            }
        }catch (e : Exception){
            e.message
        }
        Timber.d("id is $id")
        return id
    }

    override suspend fun getTransactionsByAddress(address: String): AccountTransactions? {
        val response = apiService.service.getAcccountTransactionsByAddress(address)
        try {
            if (response.isSuccessful){
                response.body()
                Timber.d("trans $response")
                Timber.d("trans ${response.body()}")
            }else{
                response.errorBody()
            }
        }catch (t : Throwable){
            t.message
        }
        return response.body()
    }

TransactionViewmodel

 private val _showMessage : MutableLiveData<String> = MutableLiveData()
    val showMessage : LiveData<String> get() = _showMessage

    private val _showProgress : MutableLiveData<Boolean> = MutableLiveData()
    val showProgress : LiveData<Boolean> get() = _showProgress

    fun transferFund(amount: Long, receiverAddress : String) {
        try {
            viewModelScope.launch {
                _showProgress.value = true
                repository.transferFund(amount, receiverAddress)
                _showProgress.value = false
            }
        }catch (e: Exception){
            _showMessage.postValue("Unable to connect to server!")
            _showProgress.postValue(false)
        }

    }

TransferFundDialog

Here, I created a dialog, where the user can enter the amount and the wallet address to do transfers. Once the transfer button is clicked this code will be initialized.

private fun validateTransfer() {
        val amount = binding.amount.text.toString().trim()
        val receiverAddress : String = binding.address.text.toString()

        if (amount.isEmpty() && receiverAddress.isEmpty()) {
            Toast.makeText(context, "Fields must be filled", Toast.LENGTH_LONG).show()
            return
        } else {
            transactionViewModel.transferFund(amount.toLong(), receiverAddress)
            dismiss()
        }
    }

7.Display list of transactions

To get list of transactions we will use the Purestake API endpoint.

AlgorandRESTService - Purestake API endpoint

 @Headers(HEADER_VALUE)
    @GET("v2/accounts/{account-id}/transactions")
    suspend fun getAcccountTransactionsByAddress(@Path("account-id") account_id : String?): Response<AccountTransactions>

TransactionRepository

    suspend fun getTransactionsByAddress(address : String) : AccountTransactions?

TransactionRepositoryImpl

 override suspend fun getTransactionsByAddress(address: String): AccountTransactions? {
        val response = apiService.service.getAcccountTransactionsByAddress(address)
        try {
            if (response.isSuccessful){
                response.body()
                Timber.d("trans $response")
                Timber.d("trans ${response.body()}")
            }else{
                response.errorBody()
            }
        }catch (t : Throwable){
            t.message
        }
        return response.body()
    }

TransactionViewmodel

    fun getTransactions(address: String) : LiveData<AccountTransactions>{
        try {
            viewModelScope.launch {
                _showProgress.value = true
                _transactions.value = repository.getTransactionsByAddress(address)
                _showProgress.value = false

            }
        }catch (e : Throwable){
            _showMessage.postValue("Unable to connect to server!")
            _showProgress.postValue(false)
        }

        return transactions
    }

Dashboard
You will create a Recyclerview Adapter to display the list of transactions

    private fun setData() {
        transactionViewModel.getTransactions(CREATOR_ADDRESS).observe(this, Observer {
            transactions = it.transactions
            Timber.d("Transactions ${transactions.size}")

            if (transactions.isNotEmpty()) {
                binding.recyclerview.visibility = View.VISIBLE
                myAdapter.setData(transactions)
                binding.emptyState.visibility = View.GONE

            } else {
                binding.recyclerview.visibility = View.GONE
                binding.emptyState.visibility = View.VISIBLE
            }
        })
    }

Fig 0-2
EditorImages/2021/08/26 00:42/Screenshot_2021-08-23_at_23.17.45.png

8.Smart contracts

Algorand Smart Contracts (ASC1) are small programs that serve various functions on the blockchain and operate on layer-1. Smart contracts are separated into two main categories, stateful and stateless.

Both types of contracts are written in the Transaction Execution Approval Language (TEAL), which is an assembly-like language that is interpreted by the Algorand Virtual Machine (AVM) running within an Algorand node.

9.Stateful Smart Contract

Stateful smart contracts are contracts that live on the chain and are used to keep track of some form of global and/or local state for the contract. For example, a voting application may be implemented as a stateful smart contract, where the list of candidates and their current vote tallies would be considered global state values. When an account casts a vote, their local account may be marked by the stateful smart contract with a boolean indicating that the account has already voted.

The Lifecycle of a Stateful Smart Contract

  • NoOp - Generic application calls to execute the ApprovalProgram

  • OptIn - Accounts use this transaction to opt into the smart contract to participate (local storage usage).

  • DeleteApplication - Transaction to delete the application.

  • UpdateApplication - Transaction to update TEAL Programs for a contract.

  • CloseOut - Accounts use this transaction to close out their participation in the contract. This call can fail based on the TEAL logic, preventing the account from removing the contract from its balance record.

  • ClearState - Similar to CloseOut, but the transaction will always clear a contract from the account’s balance record whether the program succeeds or fails.

  • The ClearStateProgram handles the ClearState transaction and the ApprovalProgram handles all other ApplicationCall transactions

The above lifecycle can be seen in the code implementation below;

StatefulContractRepository

interface StatefulContractRepository {
    suspend fun statefulSmartContract()
    suspend fun compileProgram(client: AlgodClient, programSource: ByteArray?): String?
    suspend fun waitForConfirmation(txID: String?)
    suspend fun createApp(
        client: AlgodClient,
        creator: Account,
        approvalProgramSource: TEALProgram?,
        clearProgramSource: TEALProgram?,
        globalInts: Int,
        globalBytes: Int,
        localInts: Int,
        localBytes: Int
    ): Long?
    suspend fun optInApp(client: AlgodClient, account: Account, appId: Long?) : Long
    suspend fun callApp(
        client: AlgodClient,
        account: Account,
        appId: Long?,
        appArgs: List<ByteArray>?
    ) : Long
    suspend fun readLocalState(client: AlgodClient, account: Account, appId: Long?) : String
    suspend fun readGlobalState(client: AlgodClient, account: Account, appId: Long?) : String
    suspend fun updateApp(
        client: AlgodClient,
        creator: Account,
        appId: Long?,
        approvalProgramSource: TEALProgram?,
        clearProgramSource: TEALProgram?
    ) : PendingTransactionResponse
    suspend fun closeOutApp(client: AlgodClient, userAccount: Account, appId: Long?) : PendingTransactionResponse
    suspend fun clearApp(client: AlgodClient, userAccount: Account, appId: Long?) : PendingTransactionResponse
    suspend fun deleteApp(client: AlgodClient, creatorAccount: Account, appId: Long?) : PendingTransactionResponse
}

StatefulContractRepositoryImpl

package com.africinnovate.algorandandroidkotlin.repositoryImpl

import android.os.Build
import com.africinnovate.algorandandroidkotlin.ClientService.APIService
import com.africinnovate.algorandandroidkotlin.repository.StatefulContractRepository
import com.africinnovate.algorandandroidkotlin.utils.Constants
import com.africinnovate.algorandandroidkotlin.utils.Constants.ALGOD_API_ADDR
import com.africinnovate.algorandandroidkotlin.utils.Constants.ALGOD_API_TOKEN
import com.africinnovate.algorandandroidkotlin.utils.Constants.ALGOD_PORT
import com.africinnovate.algorandandroidkotlin.utils.Constants.CREATOR_MNEMONIC
import com.africinnovate.algorandandroidkotlin.utils.Constants.USER_MNEMONIC
import com.algorand.algosdk.account.Account
import com.algorand.algosdk.crypto.Address
import com.algorand.algosdk.crypto.TEALProgram
import com.algorand.algosdk.logic.StateSchema
import com.algorand.algosdk.transaction.SignedTransaction
import com.algorand.algosdk.transaction.Transaction
import com.algorand.algosdk.transaction.Transaction.ApplicationOptInTransactionBuilder
import com.algorand.algosdk.util.Encoder.encodeToMsgPack
import com.algorand.algosdk.v2.client.common.AlgodClient
import com.algorand.algosdk.v2.client.common.Response
import com.algorand.algosdk.v2.client.model.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.commons.lang3.ArrayUtils
import timber.log.Timber
import java.nio.file.Files
import java.nio.file.Paths
import java.sql.Date
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import com.algorand.algosdk.v2.client.model.TransactionParametersResponse


class StatefulContractRepositoryImpl @Inject constructor(private val apiService: APIService) :
    StatefulContractRepository {

   private var client: AlgodClient = AlgodClient(
       ALGOD_API_ADDR,
       ALGOD_PORT,
       ALGOD_API_TOKEN,
   )

    // utility function to connect to a node
    private fun connectToNetwork(): AlgodClient {
        return client
    }

    var headers = arrayOf("X-API-Key")
    var values = arrayOf(Constants.ALGOD_API_TOKEN_KEY)

    val txHeaders: Array<String> = ArrayUtils.add(headers, "Content-Type")
    val txValues: Array<String> = ArrayUtils.add(values, "application/x-binary")

    // user declared account mnemonics
    val creatorMnemonic = CREATOR_MNEMONIC
    val userMnemonic = USER_MNEMONIC

    // declare application state storage (immutable)
    var localInts = 1
    var localBytes = 1
    var globalInts = 1
    var globalBytes = 0

    var clearProgramSource = """
        #pragma version 4
        int 1

        """.trimIndent()

    // get account from mnemonic
    var creatorAccount: Account = Account(creatorMnemonic)
    var sender: Address = creatorAccount.address

    // get node suggested parameters
    var params = client.TransactionParams().execute().body()

    // helper function to compile program source
    override suspend fun compileProgram(client: AlgodClient, programSource: ByteArray?): String? {
        lateinit var compileResponse: Response<CompileResponse>
        try {
            compileResponse = client.TealCompile().source(programSource).execute(
                headers,
                values
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
        Timber.d("compileResponse ${compileResponse.body().result}")
        return compileResponse.body().result
    }

    // utility function to wait on a transaction to be confirmed
    override suspend fun waitForConfirmation(txID: String?) {
        withContext(Dispatchers.IO) {
//            if (client == null) client = connectToNetwork()
            var lastRound: Long = client.GetStatus().execute(headers, values).body().lastRound
            while (true) {
                try {
                    // Check the pending transactions
                    val pendingInfo: Response<PendingTransactionResponse> =
                        client.PendingTransactionInformation(txID).execute(headers, values)
                    if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                        // Got the completed Transaction
                        Timber.d("Transaction   $txID +  confirmed in round  ${pendingInfo.body().confirmedRound}")
                        break
                    }
                    lastRound++
                    client.WaitForBlock(lastRound).execute(headers, values)
                } catch (e: Exception) {
                    throw e
                }
            }
        }
    }

    override suspend fun createApp(
        client: AlgodClient,
        creator: Account,
        approvalProgramSource: TEALProgram?,
        clearProgramSource: TEALProgram?,
        globalInts: Int,
        globalBytes: Int,
        localInts: Int,
        localBytes: Int
    ): Long? {

        // define sender as creator
        val sender: Address = creator.address

        // get node suggested parameters
        val params: TransactionParametersResponse? =
            client.TransactionParams().execute(headers, values).body()

        // create unsigned transaction
        val txn: Transaction = Transaction.ApplicationCreateTransactionBuilder()
            .sender(sender)
            .suggestedParams(params)
            .approvalProgram(approvalProgramSource)
            .clearStateProgram(clearProgramSource)
            .globalStateSchema(
                StateSchema(
                    globalInts,
                    globalBytes
                )
            )
            .localStateSchema(
                StateSchema(
                    localInts,
                    localBytes
                )
            )
            .build()

        // sign transaction
        val signedTxn: SignedTransaction = creator.signTransaction(txn)
        Timber.d("Signed transaction with txid: \" + ${signedTxn.transactionID}")

        var id = ""
        try {
            // Submit the transaction to the network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val rawtxresponse = client.RawTransaction().rawtxn(encodedTxBytes).execute(
                txHeaders,
                txValues
            )
            if (!rawtxresponse.isSuccessful) {
                Timber.d("raw ${rawtxresponse.message()}")
                throw Exception(rawtxresponse.message())
            } else {
              id = rawtxresponse.body().txId
                Timber.d("Successfully sent tx with ID: $id")
                waitForConfirmation(id)

            }
        }catch (e: Exception){
            e.message
        }
        // display results
        val pTrx: PendingTransactionResponse? =
            client.PendingTransactionInformation(id).execute(headers, values).body()
        val appId = pTrx?.applicationIndex
        Timber.d("Created new app-id: $appId")
        return appId
    }

    override suspend fun optInApp(client: AlgodClient, account: Account, appId: Long?) : Long {
            // declare sender
            val sender: Address = account.address
            println("OptIn from account: $sender")

            // get node suggested parameters
            val params: TransactionParametersResponse =
                client.TransactionParams().execute(headers, values).body()

            // create unsigned transaction
            val txn: Transaction = ApplicationOptInTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = account.signTransaction(txn)

            // send to network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues).body().txId

            // await confirmation
            waitForConfirmation(id)

            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("OptIn to app-id: %s", pTrx.txn.tx.applicationId)
        return pTrx.txn.tx.applicationId
    }

    override suspend fun callApp(
        client: AlgodClient,
        account: Account,
        appId: Long?,
        appArgs: List<ByteArray>?
    ) : Long{
            // declare sender
            val sender: Address = account.address
            println("Call from account: $sender")
            val params: TransactionParametersResponse =
                client.TransactionParams().execute(headers, values).body()

            // create unsigned transaction
            val txn: Transaction = Transaction.ApplicationCallTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .args(appArgs)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = account.signTransaction(txn)

            // save signed transaction to  a file
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//                Files.write(Paths.get("callArgs.stxn"), encodeToMsgPack(signedTxn))
//            }

            // send to network
          /*  val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(headers, values).body().txId

            // await confirmation
            waitForConfirmation(id)*/

            var id = ""
            try {
                // Submit the transaction to the network
                val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
                val rawtxresponse = client.RawTransaction().rawtxn(encodedTxBytes).execute(
                    txHeaders,
                    txValues
                )
                if (!rawtxresponse.isSuccessful) {
                    Timber.d("raw ${rawtxresponse.message()}")
                    throw Exception(rawtxresponse.message())
                } else {
                    id = rawtxresponse.body().txId
                    Timber.d("Successfully sent tx with ID: $id")
                    waitForConfirmation(id)
                }
            }catch (e: Exception){
                e.message
            }

            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("Called app-id: %s", pTrx.txn.tx.applicationId)
            if (pTrx.globalStateDelta != null) {
                Timber.d(" Global state: \" + pTrx.globalStateDelta.toString()")
            }
            if (pTrx.localStateDelta != null) {
                Timber.d("Local state: \" + pTrx.localStateDelta.toString()")
            }
        return pTrx.txn.tx.applicationId
    }

    override suspend fun readLocalState(client: AlgodClient, account: Account, appId: Long?) : String{
            val acctResponse: Response<com.algorand.algosdk.v2.client.model.Account> =
                client.AccountInformation(account.address).execute(headers, values)
            val applicationLocalState: List<ApplicationLocalState> =
                acctResponse.body().appsLocalState
            for (i in applicationLocalState.indices) {
                if (applicationLocalState[i].id == appId) {
                    Timber.d(
                        "User's application local state: %s",
                        applicationLocalState[i].keyValue.toString()
                    )
                }
            }
        return applicationLocalState.toString()
    }

    override suspend fun readGlobalState(client: AlgodClient, account: Account, appId: Long?) : String{
            val acctResponse: Response<com.algorand.algosdk.v2.client.model.Account> =
                client.AccountInformation(account.address).execute(headers, values)
            val createdApplications: List<Application> = acctResponse.body().createdApps
            for (i in createdApplications.indices) {
                if (createdApplications[i].id.equals(appId)) {
                    Timber.d("Application global state:  ${createdApplications[i].params.globalState.toString()}")
                }
            }
        return createdApplications.toString()
    }

    override suspend fun updateApp(
        client: AlgodClient,
        creator: Account,
        appId: Long?,
        approvalProgramSource: TEALProgram?,
        clearProgramSource: TEALProgram?
    ) : PendingTransactionResponse{
            // define sender as creator
            val sender: Address = creator.address

            // get node suggested parameters
            val params: TransactionParametersResponse =
                client.TransactionParams().execute(headers, values).body()

            // create unsigned transaction
            val txn: Transaction = Transaction.ApplicationUpdateTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .approvalProgram(approvalProgramSource)
                .clearStateProgram(clearProgramSource)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = creator.signTransaction(txn)

            // send to network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues).body().txId

            // await confirmation
            waitForConfirmation(id)

            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("Updated new app-id: $appId and $pTrx")
        return pTrx
    }


    override suspend fun closeOutApp(client: AlgodClient, userAccount: Account, appId: Long?) : PendingTransactionResponse {

            // define sender as creator
            val sender: Address = userAccount.address

            // get node suggested parameters
            val params: TransactionParametersResponse =
                client.TransactionParams().execute(headers, values).body()

            // create unsigned transaction
            val txn: Transaction = Transaction.ApplicationCloseTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = userAccount.signTransaction(txn)

            // send to network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues).body().txId

            // await confirmation
            waitForConfirmation(id)

            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("Closed out from app-id: $appId and $pTrx")
        return pTrx
    }

    override suspend fun clearApp(client: AlgodClient, userAccount: Account, appId: Long?) : PendingTransactionResponse{
            // define sender as creator
            val sender: Address = userAccount.address

            // get node suggested parameters
            val params: TransactionParametersResponse = client.TransactionParams().execute(
                headers,
                values
            ).body()

            // create unsigned transaction
            val txn: Transaction = Transaction.ApplicationClearTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = userAccount.signTransaction(txn)

            // send to network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues).body().txId
            // await confirmation
            waitForConfirmation(id)


            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("Cleared local state for app-id: $appId and $pTrx")
        return pTrx
    }

    override suspend fun deleteApp(client: AlgodClient, creatorAccount: Account, appId: Long?) : PendingTransactionResponse {
            // define sender as creator
            val sender: Address = creatorAccount.address

            // get node suggested parameters
            val params: TransactionParametersResponse = client.TransactionParams().execute(
                headers,
                values
            ).body()

            // create unsigned transaction
            val txn: Transaction = Transaction.ApplicationDeleteTransactionBuilder()
                .sender(sender)
                .suggestedParams(params)
                .applicationId(appId)
                .build()

            // sign transaction
            val signedTxn: SignedTransaction = creatorAccount.signTransaction(txn)

            // send to network
            val encodedTxBytes: ByteArray = encodeToMsgPack(signedTxn)
            val id: String =
                client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues).body().txId
            // await confirmation
            waitForConfirmation(id)


            // display results
            val pTrx: PendingTransactionResponse =
                client.PendingTransactionInformation(id).execute(headers, values).body()
            Timber.d("Deleted app-id: $appId and $pTrx")
        return pTrx

    }

    override suspend fun statefulSmartContract() {
        // user declared account mnemonics
        val creatorMnemonic = CREATOR_MNEMONIC
        val userMnemonic = USER_MNEMONIC

        // declare application state storage (immutable)
        val localInts = 1
        val localBytes = 1
        val globalInts = 1
        val globalBytes = 0

        // user declared approval program (initial)
        val approvalProgramSourceInitial = """
            #pragma version 2
            ///// Handle each possible OnCompletion type. We don't have to worry about
            //// handling ClearState, because the ClearStateProgram will execute in that
            //// case, not the ApprovalProgram.
            txn OnCompletion
            int NoOp
            ==
            bnz handle_noop
            txn OnCompletion
            int OptIn
            ==
            bnz handle_optin
            txn OnCompletion
            int CloseOut
            ==
            bnz handle_closeout
            txn OnCompletion
            int UpdateApplication
            ==
            bnz handle_updateapp
            txn OnCompletion
            int DeleteApplication
            ==
            bnz handle_deleteapp
            //// Unexpected OnCompletion value. Should be unreachable.
            err
            handle_noop:
            //// Handle NoOp
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            bnz handle_optin
            //// read global state
            byte "counter"
            dup
            app_global_get
            //// increment the value
            int 1
            +
            //// store to scratch space
            dup
            store 0
            //// update global state
            app_global_put
            //// read local state for sender
            int 0
            byte "counter"
            app_local_get
            //// increment the value
            int 1
            +
            store 1
            //// update local state for sender
            int 0
            byte "counter"
            load 1
            app_local_put
            //// load return value as approval
            load 0
            return
            handle_optin:
            //// Handle OptIn
            //// approval
            int 1
            return
            handle_closeout:
            //// Handle CloseOut
            ////approval
            int 1
            return
            handle_deleteapp:
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            return
            handle_updateapp:
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            return

            """.trimIndent()

        // user declared approval program (refactored)
        val approvalProgramSourceRefactored = """
            #pragma version 2
            //// Handle each possible OnCompletion type. We don't have to worry about
            //// handling ClearState, because the ClearStateProgram will execute in that
            //// case, not the ApprovalProgram.
            txn OnCompletion
            int NoOp
            ==
            bnz handle_noop
            txn OnCompletion
            int OptIn
            ==
            bnz handle_optin
            txn OnCompletion
            int CloseOut
            ==
            bnz handle_closeout
            txn OnCompletion
            int UpdateApplication
            ==
            bnz handle_updateapp
            txn OnCompletion
            int DeleteApplication
            ==
            bnz handle_deleteapp
            //// Unexpected OnCompletion value. Should be unreachable.
            err
            handle_noop:
            //// Handle NoOp
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            bnz handle_optin
            //// read global state
            byte "counter"
            dup
            app_global_get
            //// increment the value
            int 1
            +
            //// store to scratch space
            dup
            store 0
            //// update global state
            app_global_put
            //// read local state for sender
            int 0
            byte "counter"
            app_local_get
            //// increment the value
            int 1
            +
            store 1
            //// update local state for sender
            //// update "counter"
            int 0
            byte "counter"
            load 1
            app_local_put
            //// update "timestamp"
            int 0
            byte "timestamp"
            txn ApplicationArgs 0
            app_local_put
            //// load return value as approval
            load 0
            return
            handle_optin:
            //// Handle OptIn
            //// approval
            int 1
            return
            handle_closeout:
            //// Handle CloseOut
            ////approval
            int 1
            return
            handle_deleteapp:
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            return
            handle_updateapp:
            //// Check for creator
            addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
            txn Sender
            ==
            return

            """.trimIndent()

        // declare clear state program source
        val clearProgramSource = """
            #pragma version 2
            int 1

            """.trimIndent()
        withContext(Dispatchers.IO) {
            try {
                // Create an algod client
//                if (client == null) client = connectToNetwork()

                // get accounts from mnemonic
                val creatorAccount = Account(creatorMnemonic)
                val userAccount = Account(userMnemonic)

                // compile programs
                var approvalProgram = compileProgram(
                    client,
                    approvalProgramSourceInitial.toByteArray(charset("UTF-8"))
                )
                val clearProgram =
                    compileProgram(client, clearProgramSource.toByteArray(charset("UTF-8")))

                // create new application
                val appId = createApp(
                    client,
                    creatorAccount,
                    TEALProgram(approvalProgram),
                    TEALProgram(clearProgram),
                    globalInts,
                    globalBytes,
                    localInts,
                    localBytes
                )

                // opt-in to application
                optInApp(client, userAccount, appId)

                // call application without arguments
                callApp(client, userAccount, appId, null)

                // read local state of application from user account
                readLocalState(client, userAccount, appId)

                // read global state of application
                readGlobalState(client, creatorAccount, appId)

                // update application
                approvalProgram = compileProgram(
                    client,
                    approvalProgramSourceRefactored.toByteArray(charset("UTF-8"))
                )
                updateApp(
                    client,
                    creatorAccount,
                    appId,
                    TEALProgram(approvalProgram),
                    TEALProgram(clearProgram)
                )

                // call application with arguments
                val formatter = SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss")
                val date = Date(System.currentTimeMillis())
                Timber.d("${formatter.format(date)}")
                val appArgs: MutableList<ByteArray> = ArrayList()
                appArgs.add(formatter.format(date).toString().toByteArray(charset("UTF8")))
                callApp(client, userAccount, appId, appArgs)

                // read local state of application from user account
                readLocalState(client, userAccount, appId)

                // close-out from application
                closeOutApp(client, userAccount, appId)

                // opt-in again to application
                optInApp(client, userAccount, appId)

                // call application with arguments
                callApp(client, userAccount, appId, appArgs)

                // read local state of application from user account
                readLocalState(client, userAccount, appId)

                // delete application
                deleteApp(client, creatorAccount, appId)

                // clear application from user account
                clearApp(client, userAccount, appId)
            } catch (e: Exception) {
                System.err.println("Exception raised: " + e.message)
            }
        }
    }

}

StatefulSmartContractViewModel

   fun statefulSmartContract(){
        viewModelScope.launch {
            repository.statefulSmartContract()
        }
    }

    fun compileProgram(client: AlgodClient, programSource: ByteArray?) : LiveData<String> {
        viewModelScope.launch {
            try {
                _response.value = repository.compileProgram(client, programSource)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }

    fun createApp(client: AlgodClient,
                  creator: Account,
                  approvalProgramSource: TEALProgram?,
                  clearProgramSource: TEALProgram?,
                  globalInts: Int,
                  globalBytes: Int,
                  localInts: Int,
                  localBytes: Int
    ): LiveData<Long>?{
        viewModelScope.launch {
            try {
                _appid.value = repository.createApp(client,creator, approvalProgramSource, clearProgramSource,globalInts,globalBytes,localInts,localBytes)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return appid
    }

    fun optInApp(client: AlgodClient, userAccount: Account, apppId: Long?) : LiveData<Long>{
        viewModelScope.launch {
            try {
                _appid.value = repository.optInApp(client,userAccount, apppId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return appid
    }

    fun callApp(client: AlgodClient,
                userAccount: Account,
                appId: Long?,
                appArgs: List<ByteArray>?) : LiveData<Long>{
        viewModelScope.launch {
            try {
                _appid.value = repository.callApp(client,userAccount, appId, appArgs)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return appid
    }

    fun readLocalState(client: AlgodClient, userAccount: Account, appId: Long?) : LiveData<String>{
        viewModelScope.launch {
            try {
                _response.value = repository.readLocalState(client,userAccount, appId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }

    fun readGlobalState(client: AlgodClient, userAccount: Account, appId: Long?) : LiveData<String>{
        viewModelScope.launch {
            try {
                _response.value = repository.readGlobalState(client,userAccount, appId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }

    fun updateApp(client: AlgodClient,
                  creator: Account,
                  appId: Long?,
                  approvalProgramSource: TEALProgram?,
                  clearProgramSource: TEALProgram?) : LiveData<PendingTransactionResponse>{
        viewModelScope.launch {
            try {
                _pTrx.value = repository.updateApp(client,creator, appId, approvalProgramSource, clearProgramSource)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return pTrx
    }

    fun closeOutApp(client: AlgodClient, userAccount: Account, appId: Long?) : LiveData<PendingTransactionResponse>{
        viewModelScope.launch {
            try {
                _pTrx.value = repository.closeOutApp(client,userAccount, appId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return pTrx
    }

    fun clearApp(client: AlgodClient, userAccount: Account, appId: Long?) : LiveData<PendingTransactionResponse>{
        viewModelScope.launch {
            try {
                _pTrx.value = repository.clearApp(client,userAccount, appId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return pTrx
    }

    fun deleteApp(client: AlgodClient, userAccount: Account, appId: Long?) : LiveData<PendingTransactionResponse>{
        viewModelScope.launch {
            try {
                _pTrx.value = repository.deleteApp(client,userAccount, appId)
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return pTrx
    }

MainActivity

On StatefulSmartContract button clicked this method will be called and results will be displayed on the console

        binding.stflContract.setOnClickListener {
            val intent = Intent(this, StatefulActivity::class.java)
            startActivity(intent)
        }

StatefulActivity

      @AndroidEntryPoint
class StatefulActivity : AppCompatActivity() {
    private val viewModel: StatefulSmartContractViewModel by viewModels()
    private lateinit var binding: ActivityStatefulBinding
    private var client: AlgodClient = AlgodClient(
        Constants.ALGOD_API_ADDR,
        Constants.ALGOD_PORT,
        Constants.ALGOD_API_TOKEN,
    )

    // compile programs
    var approvalProgram: String = ""
    var approvalProgram2: String = ""

    var clearProgram: String = ""
    var appId: Long = 0L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_stateful)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_stateful)
        binding.compile.setOnClickListener {
            viewModel.compileProgram(
                client,
                approvalProgramSourceInitial.toByteArray(charset("UTF-8"))
            ).observe(this, {
                binding.stflOutput.text = it
            })
        }

        viewModel.compileProgram(
            client,
            approvalProgramSourceRefactored.toByteArray(charset("UTF-8"))
        ).observe(this, {
            approvalProgram = it
        })

        viewModel.compileProgram(
            client,
            approvalProgramSourceInitial.toByteArray(charset("UTF-8"))
        ).observe(this, {
            approvalProgram2 = it
        })

        viewModel.compileProgram(
            client,
            clearProgramSource.toByteArray(charset("UTF-8"))
        ).observe(this, {
            clearProgram = it

        })

        binding.createApp.setOnClickListener {
            createApp()
        }
        binding.optInApp.setOnClickListener { optionApp() }
        binding.callApp.setOnClickListener { callApp() }
        binding.readLocalState.setOnClickListener { readLocalState() }
        binding.readGlobalState.setOnClickListener { readGlobalState() }
        binding.updateApp.setOnClickListener { updateApp() }
        binding.closeOutApp.setOnClickListener { closeOutApp() }
        binding.clearApp.setOnClickListener { clearApp() }
        binding.deleteApp.setOnClickListener { deleteApp() }
         binding.stateful.setOnClickListener {
            viewModel.statefulSmartContract()
        }
    }


   private fun createApp(){
        val approve = intent.getStringExtra("approval")
        val clear = intent.getStringExtra("clear")
        Timber.d("program1 $approve")
        Timber.d("clear1 $clear")

        viewModel.createApp(
            client, creatorAccount, TEALProgram(approvalProgram),
            TEALProgram(clearProgram),
            globalInts,
            globalBytes,
            localInts,
            localBytes
        )?.observe(this, {
            appId = it
            Timber.d("appId $appId")
            binding.stflOutput.text = "Created app id: $it"

        })
    }

    private fun optionApp() {
        viewModel.optInApp(client, userAccount, appId).observe(this, { appId ->
            binding.stflOutput.text = "OptIn from account: ${userAccount.address}\n OptIn to app-id: $appId"
        })
    }

    private fun callApp() {
        viewModel.callApp(client, userAccount, appId, null).observe(this, { appId ->
            binding.stflOutput.text = " Call from account: ${userAccount.address}\n Called app-id: $appId"
        })
    }

    private fun readLocalState() {
        viewModel.readLocalState(client, userAccount, appId).observe(this, { appId ->
            binding.stflOutput.text = " User's Application Local State: $appId"
        })
    }

    private fun readGlobalState() {
        viewModel.readGlobalState(client, userAccount, appId).observe(this, { appId ->
            binding.stflOutput.text = "Application Global State: $appId"
        })
    }

    private fun updateApp() {
        viewModel.updateApp(
            client, creatorAccount, appId, TEALProgram(approvalProgram2),
            TEALProgram(clearProgram)
        ).observe(this, { ptr ->
            binding.stflOutput.text = "Updated new app-id: $appId and $ptr"
        })
    }

    private fun closeOutApp(){
        viewModel.closeOutApp(
            client, userAccount, appId
        ).observe(this, { ptr ->
            binding.stflOutput.text = "Closed out from  app-id: $appId and $ptr"
        })
    }

    private fun clearApp(){
        viewModel.clearApp(
            client, userAccount, appId
        ).observe(this, { ptr ->
            binding.stflOutput.text = "Cleared local state for  app-id: $appId and ${ptr}"
        })
    }

    private fun deleteApp(){
        viewModel.deleteApp(
            client, creatorAccount, appId
        ).observe(this, { ptr ->
            binding.stflOutput.text = "Deleted app-id: $appId and $ptr"
        })
    }


    companion object {
        // user declared account mnemonics
        val creatorMnemonic = Constants.CREATOR_MNEMONIC
        val userMnemonic = Constants.USER_MNEMONIC

        // get accounts from mnemonic
        val creatorAccount = Account(creatorMnemonic)
        val userAccount = Account(userMnemonic)

        // declare application state storage (immutable)
        val localInts = 1
        val localBytes = 1
        val globalInts = 1
        val globalBytes = 0

        // user declared approval program (initial)
        val approvalProgramSourceInitial = """
            #pragma version 2
            ///// Handle each possible OnCompletion type. We don't have to worry about
            //// handling ClearState, because the ClearStateProgram will execute in that
            //// case, not the ApprovalProgram.
            txn OnCompletion
            int NoOp
            ==
            bnz handle_noop
            txn OnCompletion
            int OptIn
            ==
            bnz handle_optin
            txn OnCompletion
            int CloseOut
            ==
            bnz handle_closeout
            txn OnCompletion
            int UpdateApplication
            ==
            bnz handle_updateapp
            txn OnCompletion
            int DeleteApplication
            ==
            bnz handle_deleteapp
            //// Unexpected OnCompletion value. Should be unreachable.
            err
            handle_noop:
            //// Handle NoOp
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            bnz handle_optin
            //// read global state
            byte "counter"
            dup
            app_global_get
            //// increment the value
            int 1
            +
            //// store to scratch space
            dup
            store 0
            //// update global state
            app_global_put
            //// read local state for sender
            int 0
            byte "counter"
            app_local_get
            //// increment the value
            int 1
            +
            store 1
            //// update local state for sender
            int 0
            byte "counter"
            load 1
            app_local_put
            //// load return value as approval
            load 0
            return
            handle_optin:
            //// Handle OptIn
            //// approval
            int 1
            return
            handle_closeout:
            //// Handle CloseOut
            ////approval
            int 1
            return
            handle_deleteapp:
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            return
            handle_updateapp:
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            return

            """.trimIndent()

        // user declared approval program (refactored)
        val approvalProgramSourceRefactored = """
            #pragma version 2
            //// Handle each possible OnCompletion type. We don't have to worry about
            //// handling ClearState, because the ClearStateProgram will execute in that
            //// case, not the ApprovalProgram.
            txn OnCompletion
            int NoOp
            ==
            bnz handle_noop
            txn OnCompletion
            int OptIn
            ==
            bnz handle_optin
            txn OnCompletion
            int CloseOut
            ==
            bnz handle_closeout
            txn OnCompletion
            int UpdateApplication
            ==
            bnz handle_updateapp
            txn OnCompletion
            int DeleteApplication
            ==
            bnz handle_deleteapp
            //// Unexpected OnCompletion value. Should be unreachable.
            err
            handle_noop:
            //// Handle NoOp
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            bnz handle_optin
            //// read global state
            byte "counter"
            dup
            app_global_get
            //// increment the value
            int 1
            +
            //// store to scratch space
            dup
            store 0
            //// update global state
            app_global_put
            //// read local state for sender
            int 0
            byte "counter"
            app_local_get
            //// increment the value
            int 1
            +
            store 1
            //// update local state for sender
            //// update "counter"
            int 0
            byte "counter"
            load 1
            app_local_put
            //// update "timestamp"
            int 0
            byte "timestamp"
            txn ApplicationArgs 0
            app_local_put
            //// load return value as approval
            load 0
            return
            handle_optin:
            //// Handle OptIn
            //// approval
            int 1
            return
            handle_closeout:
            //// Handle CloseOut
            ////approval
            int 1
            return
            handle_deleteapp:
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            return
            handle_updateapp:
            //// Check for creator
            addr FR23WI5ZTTRNYTXHA73GIJNS6BDXR3PZA6WETQF7IO6YBBYBS27TXUDNPI
            txn Sender
            ==
            return

            """.trimIndent()

        // declare clear state program source
        val clearProgramSource = """
            #pragma version 2
            int 1

            """.trimIndent()
    }
}

Screenshots of log output from Stateful Smart Contract
Fig 0-3
EditorImages/2021/08/26 00:44/Screenshot_2021-08-23_at_10.33.07.png

Fig 0-4
EditorImages/2021/08/26 00:45/Screenshot_2021-08-23_at_10.33.30.png

Fig 0-5
EditorImages/2021/08/26 00:45/Screenshot_2021-08-23_at_10.33.35.png

10.Stateless Smart Contract

Stateless smart contracts are primarily used to replace signature authority for transactions. All transactions in Algorand must be signed, by either an account or a multi-signature account.

With stateless smart contracts, transactions can also be signed with logic, which Algorand designates as a LogicSignature. Stateless smart contracts can further be broken into two primary modes of use: contract accounts, and signature delegation.

Below is the code representation of a stateless smart contract.

StatelessContractRepository

interface StateLessContractRepository {
    suspend fun compileTealSource() : CompileResponse
    suspend fun waitForConfirmation(txID: String)
    suspend fun contractAccountExample() : CompileResponse
    suspend fun accountDelegationExample() : CompileResponse
}

StatelessSmartContractRepositoryImpl

package com.africinnovate.algorandandroidkotlin.repositoryImpl

import android.os.Build
import androidx.annotation.RequiresApi
import com.africinnovate.algorandandroidkotlin.ClientService.APIService
import com.africinnovate.algorandandroidkotlin.repository.StateLessContractRepository
import com.africinnovate.algorandandroidkotlin.utils.Constants
import com.africinnovate.algorandandroidkotlin.utils.Constants.CREATOR_MNEMONIC
import com.africinnovate.algorandandroidkotlin.utils.Constants.USER_ADDRESS
import com.algorand.algosdk.account.Account
import com.algorand.algosdk.algod.client.ApiException
import com.algorand.algosdk.crypto.Address
import com.algorand.algosdk.crypto.LogicsigSignature
import com.algorand.algosdk.transaction.SignedTransaction
import com.algorand.algosdk.transaction.Transaction
import com.algorand.algosdk.util.Encoder
import com.algorand.algosdk.v2.client.common.AlgodClient
import com.algorand.algosdk.v2.client.common.Response
import com.algorand.algosdk.v2.client.model.PendingTransactionResponse
import org.apache.commons.lang3.ArrayUtils
import org.json.JSONObject
import timber.log.Timber
import java.nio.file.Files.readAllBytes
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.inject.Inject


class StateLessSmartContractRepositoryImpl @Inject constructor(private val apiService: APIService) :
    StateLessContractRepository {
    private var client: AlgodClient = AlgodClient(
        ALGOD_API_ADDR,
        Constants.ALGOD_PORT,
        Constants.ALGOD_API_TOKEN,
    )

    // utility function to connect to a node
    private fun connectToNetwork(): AlgodClient {
        return client
    }

    var headers = arrayOf("X-API-Key")
    var values = arrayOf(Constants.ALGOD_API_TOKEN_KEY)

    val txHeaders: Array<String> = ArrayUtils.add(headers, "Content-Type")
    val txValues: Array<String> = ArrayUtils.add(values, "application/x-binary")

    override suspend fun compileTealSource(): CompileResponse {
        // Initialize an algod client
        if (client == null) client = connectToNetwork()

        // read file - int 0
//      val data: ByteArray = Files.readAllBytes(Paths.get("/sample.teal"))
//        val data = byteArrayOf(0)
        val data1 = "int 0"

//        Timber.d("data : ${data.contentToString()}")

        val response: CompileResponse = client.TealCompile().source(data1.toByteArray(charset("UTF-8"))).execute(headers,values).body()
        // print results
        Timber.d("response: $response")
        Timber.d("Hash: " + response.hash)
        Timber.d("Result: " + response.result)
        return response
    }

    override suspend fun waitForConfirmation(txID: String) {
        if (client == null) client = connectToNetwork()
        var lastRound = client.GetStatus().execute(headers, values).body().lastRound
        while (true) {
            try {
                // Check the pending transactions
                val pendingInfo: Response<PendingTransactionResponse> =
                    client.PendingTransactionInformation(txID).execute(headers, values)
                if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                    // Got the completed Transaction
                    println(
                        "Transaction " + txID + " confirmed in round " + pendingInfo.body().confirmedRound
                    )
                    break
                }
                lastRound++
                client.WaitForBlock(lastRound).execute(headers, values)
            } catch (e: Exception) {
                throw e
            }
        }
    }

    override suspend fun contractAccountExample() : CompileResponse{
        // Initialize an algod client
        if (client == null) client = connectToNetwork()

        // Set the receiver
        val RECEIVER = "QUDVUXBX4Q3Y2H5K2AG3QWEOMY374WO62YNJFFGUTMOJ7FB74CMBKY6LPQ"

        // Read program from file samplearg.teal
//        val source = readAllBytes(Paths.get("./samplearg.teal"))
        val source = """
            arg_0
            btoi
            int 123
            ==
        """.trimIndent()
        // compile
        val response = client.TealCompile().source(source.toByteArray(charset("UTF-8"))).execute(headers, values).body()
        // print results
        println("response: $response")
        println("Hash: " + response.hash)
        println("Result: " + response.result)
        val program = Base64.getDecoder().decode(response.result.toString())

        // create logic sig
        // integer parameter
        val teal_args = ArrayList<ByteArray>()
        val arg1 = byteArrayOf(123)
        teal_args.add(arg1)
        val lsig = LogicsigSignature(program, teal_args)
        // For no args use null as second param
        // LogicsigSignature lsig = new LogicsigSignature(program, null);
        println("lsig address: " + lsig.toAddress())
        val params = client.TransactionParams().execute(headers, values).body()
        // create a transaction
        val note = "Hello World"
        val txn: Transaction = Transaction.PaymentTransactionBuilder()
            .sender(
                lsig
                    .toAddress()
            )
            .note(note.toByteArray())
            .amount(100000)
            .receiver(Address(RECEIVER))
            .suggestedParams(params)
            .build()
        try {
            // create the LogicSigTransaction with contract account LogicSig
            val stx: SignedTransaction = Account.signLogicsigTransaction(lsig, txn)
            // send raw LogicSigTransaction to network
            val encodedTxBytes: ByteArray = Encoder.encodeToMsgPack(stx)
            val id = client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues)
                .body().txId
            // Wait for transaction confirmation
            waitForConfirmation(id)
            println("Successfully sent tx with id: $id")
            // Read the transaction
            val pTrx = client.PendingTransactionInformation(id).execute(headers, values).body()
            val jsonObj = JSONObject(pTrx.toString())
            println("Transaction information (with notes): " + jsonObj.toString(2)) // pretty print
            println("Decoded note: " + String(pTrx.txn.tx.note))
        } catch (e: ApiException) {
            System.err.println("Exception when calling algod#rawTransaction: " + e.getResponseBody())
        }
        return response
    }

    override suspend fun accountDelegationExample() : CompileResponse{
        // Initialize an algod client
        if (client == null) client = connectToNetwork()
        // import your private key mnemonic and address
        val SRC_ACCOUNT =
            "buzz genre work meat fame favorite rookie stay tennis demand panic busy hedgehog snow morning acquire ball grain grape member blur armor foil ability seminar"

        val src = Account(SRC_ACCOUNT)
        // Set the receiver
        val RECEIVER = "QUDVUXBX4Q3Y2H5K2AG3QWEOMY374WO62YNJFFGUTMOJ7FB74CMBKY6LPQ"

        // Read program from file samplearg.teal
//        val source = readAllBytes(Paths.get("./samplearg.teal"))
        val source = """
            arg_0
            btoi
            int 123
            ==
        """.trimIndent()
        // compile
        val response = client.TealCompile().source(source.toByteArray(charset("UTF-8"))).execute(headers, values).body()
        // print results
        println("response: $response")
        println("Hash: " + response.hash)
        println("Result: " + response.result)
        val program = Base64.getDecoder().decode(response.result.toString())

        // create logic sig

        // string parameter
        // ArrayList<byte[]> teal_args = new ArrayList<byte[]>();
        // String orig = "my string";
        // teal_args.add(orig.getBytes());
        // LogicsigSignature lsig = new LogicsigSignature(program, teal_args);

        // integer parameter
        val teal_args = ArrayList<ByteArray>()
        val arg1 = byteArrayOf(123)
        teal_args.add(arg1)
        val lsig = LogicsigSignature(program, teal_args)
        //    For no args use null as second param
        //    LogicsigSignature lsig = new LogicsigSignature(program, null);
        // sign the logic signature with an account sk
        src.signLogicsig(lsig)
        val params = client.TransactionParams().execute(headers, values).body()
        // create a transaction
        val note = "Hello World"
        val txn = Transaction.PaymentTransactionBuilder()
            .sender(src.address)
            .note(note.toByteArray())
            .amount(100000)
            .receiver(Address(RECEIVER))
            .suggestedParams(params)
            .build()
        try {
            // create the LogicSigTransaction with contract account LogicSig
            val stx = Account.signLogicsigTransaction(lsig, txn)
            // send raw LogicSigTransaction to network
            val encodedTxBytes = Encoder.encodeToMsgPack(stx)
            val id = client.RawTransaction().rawtxn(encodedTxBytes).execute(txHeaders, txValues)
                .body().txId
            // Wait for transaction confirmation
            waitForConfirmation(id)
            println("Successfully sent tx with id: $id")
            // Read the transaction
            val pTrx = client.PendingTransactionInformation(id).execute(headers, values).body()
            val jsonObj = JSONObject(pTrx.toString())
            println("Transaction information (with notes): " + jsonObj.toString(2)) // pretty print
            println("Decoded note: " + String(pTrx.txn.tx.note))
        } catch (e: ApiException) {
            System.err.println("Exception when calling algod#rawTransaction: " + e.responseBody)
        }
        return response
    }

StatelessSmartContractViewModel

class StatelessSmartContractViewModel @ViewModelInject
constructor(val repository: StateLessContractRepository) : ViewModel() {
    private val _response : MutableLiveData<CompileResponse> = MutableLiveData()
    val response : LiveData<CompileResponse> get() = _response

    fun compileTealSource() : LiveData<CompileResponse> {
        viewModelScope.launch {
            try {
            _response.value = repository.compileTealSource()
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }

    fun contractAccount() : LiveData<CompileResponse> {
        viewModelScope.launch {
            try {
              _response.value = repository.contractAccountExample()
            } catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }

    fun accountDelegation() : LiveData<CompileResponse> {
        viewModelScope.launch {
            try {
               _response.value = repository.accountDelegationExample()
            }catch (e: Exception) {
                Timber.e(e)
            }
        }
        return response
    }
}

MainActivity
Onclick on the Stateless Smart Contract button this method will be called inside the oncreate. The resulting output will be displayed on the console.

  binding.stlssContract.setOnClickListener {
            val intent = Intent(this, StatelessActivity::class.java)
            startActivity(intent)
        }
@AndroidEntryPoint
class StatelessActivity : AppCompatActivity() {
    private val viewModel: StatelessSmartContractViewModel by viewModels()
    private lateinit var binding: ActivityStatelessBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_stateless)

        binding.compile.setOnClickListener {
            viewModel.compileTealSource().observe(this, { response ->
                binding.output.text = " response :$response\n  hash: ${response.hash}\n  result: ${response.result}  "
            })
        }

        binding.contractAccount.setOnClickListener {
            viewModel.contractAccount().observe(this, { response ->
                binding.output.text = " response :$response\n  hash: ${response.hash}\n  result: ${response.result}  "
            })
        }

        binding.accountDelegation.setOnClickListener {
            viewModel.accountDelegation().observe(this, { response ->
                binding.output.text = " response :$response\n  hash: ${response.hash}\n  result: ${response.result}  "
            })
        }
    }

}

Screenshots of log output from Stateless Smart Contract

Fig 0-6
EditorImages/2021/08/26 00:47/Screenshot_2021-08-23_at_22.43.46.png

Fig 0-7
EditorImages/2021/08/26 00:48/Screenshot_2021-08-23_at_22.44.28.png

Fig 0-8
EditorImages/2021/08/26 00:48/Screenshot_2021-08-23_at_22.45.39.png

11.Full code

The tutorial is already lengthy, so you can get the full code from the github repo
Here is the link to the AlgorandKotlinWallet

12.What’s next?

  • Creating an asset, Modifying, freezing, transferring and destroying

  • Teal, and more on smart contracts

  • Algorand Dapps

  • Connecting to AlgoSigner, Algo connect and Wallet connect

13.Resources

14.Conclusion

Most blockchain services, smart contracts, dapps will not be complete without thinking mobile first interactions. And building natively for the Android platform attention is gradually shifting from Java to Kotlin, good thing Java codes can be easily converted to Kotlin.

Most mobile Native Android apps will be done using Kotlin. There is therefore need to create adequate documentation that will meet the needs of Kotlin Native Android Developers.

In this tutorial we were able to cover, creating account, account recovery, getting account balance, transactions, stateful and stateless smart contracts. In subsequent, tutorials we will dig deep into more of the other services provided by the Algorand SDK, Purestake API and Algoexplorer.

Warning

This tutorial is intended for learning purposes only. It does not cover error checking and other edge cases. The smart contract(s) in this solution have NOT been audited. Therefore, it should not be used as a production application.