Scripting


In this section, we prepare the TypeScript for connecting the frontend with the Scilla contract. As discussed, we use ZilPay to access the Zilliqa JS utilities, which in turn allows us to interface with the deployed contract.

Environment Variables

We store the address of the smart contract in an environment variable in an .env file. We also store a Google Maps API Key for embedding a Map on the Individual Listing page.

REACT_APP_SMART_CONTRACT_ADDRESS=zil1tug5k2la6xrjqc78ysfacskgt2m72uzdwmd86z
REACT_APP_MAPS_API_KEY=XXXXXXXXXXXXXXXX

Helper Functions

We create a bunch of helper functions to facilitate the contract connection. We will define them in the /src/functions/ directory.

ContextContainer

We use unstated-next to create a Context Provider for the application. We use this to make some objects available across all the components of the application. We will see how this is used in the coming sections.

import { useState } from "react";
import { createContainer } from "unstated-next";
const useContext = () => {
const [zilPay, setZilPay] = useState<any | undefined>(undefined);
const [listings, setListings] = useState<any | undefined>(undefined);
const [error, setError] = useState<boolean | undefined>(undefined);
const [contract, setContract] = useState<any | undefined>(undefined);
const [contractState, setContractState] =
useState<any | undefined>(undefined);
const [currentUser, setCurrentUser] = useState<any | undefined>(undefined);
const [currentBlockNumber, setCurrentBlockNumber] =
useState<number | undefined>(undefined);
return {
zilPay,
setZilPay,
listings,
setListings,
error,
setError,
contract,
setContract,
contractState,
setContractState,
currentUser,
setCurrentUser,
currentBlockNumber,
setCurrentBlockNumber,
};
};
const ContextContainer = createContainer(useContext);
export default ContextContainer;

/src/functions/contextContainer.ts


getCallParameters

This function returns an object with the parameters required for calling transitions. An optional amountValue argument can be used when sending messages with a non-zero amount. The values are converted to the appropriate units using zilPay.utils.

const getCallParameters = (zilPay: any, amountValue: string = "0") => {
const { units, bytes } = zilPay.utils;
const CHAIN_ID = 333;
const MSG_VERSION = 1;
const GAS_PRICE = 60000000000;
const GAS_LIMIT = 50000;
const version = bytes.pack(CHAIN_ID, MSG_VERSION);
const amount = units.toQa(amountValue, units.Units.Zil);
const gasPrice = units.fromQa(GAS_PRICE, units.Units.Qa);
const gasLimit = units.fromQa(GAS_LIMIT, units.Units.Qa);
return { version, amount, gasPrice, gasLimit };
};
export default getCallParameters;

/src/functions/getCallParameters.ts


getCurrentUser

This function fetches the wallet address of the active ZilPay user. It gets the name and role of the user from the contract state.

const getCurrentUser = (contractState: any, zilPay: any) => {
const currentUser = zilPay.wallet.defaultAccount.base16;
const address = currentUser.toLowerCase();
const name = contractState.user_name[address];
const role = contractState.user_role?.[address] === "1" ? "host" : "renter";
return { address, name, role };
};
export default getCurrentUser;

/src/functions/getCurrentUser.ts


getCurrentEpochNumber

This function fetches the current mini epoch suing ZilPay.

const getCurrentEpochNumber = async (zilPay: any) => {
const data = await zilPay.blockchain.getCurrentMiniEpoch();
return await data.result;
};
export default getCurrentEpochNumber;

/src/functions/getCurrentEpochNumber.ts


formatListings

This function takes the multiple Map objects in the contract state and returns a handy user object. It checks if the current ZilPay user's wallet address matches that of the host of the listing. It also checks the rented status of the listing. Prices and rent are converted from Qa. Amenities are converted to boolean.

const formatListings = (
contractState: any,
currentEpochNumber: number,
currentUser: string
) => {
const {
listing_name,
listing_description,
listing_host,
listing_price,
listing_rooms,
listing_bathrooms,
listing_image,
listing_location,
listing_renter,
listing_rented_till,
listing_accumulated_rent,
listing_wifi,
listing_kitchen,
listing_tv,
listing_laundry,
listing_hvac,
} = contractState;
const formattedListings = Object.keys(listing_name).map(
(key: any, index: any) => {
return {
id: key,
name: listing_name[key],
description: listing_description[key],
price: (parseInt(listing_price[key]) / 10 ** 12).toString(),
rooms: listing_rooms[key],
bathrooms: listing_bathrooms[key],
image: listing_image[key],
location: listing_location[key],
renter: listing_renter[key],
rented_till: listing_rented_till[key],
accumulated_rent: (
parseInt(listing_accumulated_rent[key]) /
10 ** 12
).toString(),
rented:
parseInt(listing_rented_till[key]) >= currentEpochNumber,
user_is_host: listing_host[0] === currentUser.toLowerCase(),
amenities: {
wifi: listing_wifi[key] === "yes",
kitchen: listing_kitchen[key] === "yes",
tv: listing_tv[key] === "yes",
laundry: listing_laundry[key] === "yes",
hvac: listing_hvac[key] === "yes",
},
};
}
);
return formattedListings;
};
export default formatListings;

/src/functions/formatListings.ts


transitionMessageAlert

This function creates a toast using react-hot-toast, It uses zilPay.wallet to subscribe to transactions. It updates toast with the status of the transaction and shows a message as per the Messages Codes we defined earlier.

Note that in this function, we use another helper function, decodeMessage, to get a human-readable message from the message code. This function is quite basic and hence not included here. You can take a look at /src/functions/decodeMessage.ts. It also includes a decodeZilPayError function that we will use in the coming sections.

import toast from "react-hot-toast";
import decodeMessage from "./decodeMessage";
const transitionMessageAlert = (
zilPay: any,
transactionId: string,
transitionName: string
) => {
const transition = new Promise<string>((success, error) => {
const subscription = zilPay.wallet
.observableTransaction(transactionId)
.subscribe(async (hash: any) => {
subscription.unsubscribe();
try {
const Tx = await zilPay.blockchain.getTransaction(hash[0]);
const code = Tx.receipt.transitions[0].msg.params[0].value;
const message = decodeMessage(code);
if (message.type === "success") success(message.alert);
error(message.alert);
} catch (err) {
error("Transaction error");
}
});
});
toast.promise(transition, {
loading: `${transitionName}`,
success: (message: string) => message,
error: (message: string) => message,
});
};
export default transitionMessageAlert;

/src/functions/transitionMessageAlert.ts


Transition Functions

We finally come to the Transition Functions that simply call the contract transitions using zilPay.contract. The transitionMessageAlert is also setup after the transitions are triggered.

The following functions are created at /src/functions/ for calling their respective transitions.

FunctionTransition
createUserTransitioncreate_user
createListingTransitioncreate_listing
updateListingTransitionupdate_listing
deleteListingTransitiondelete_listing
bookListingTransitionbook_listing
claimRentTransitionclaim_rent

Let us see the createUserTransition function as an example. We use the decodeZilPayError we defined earlier.

import getCallParameters from "./getCallParameters";
import toast from "react-hot-toast";
import transitionMessageAlert from "./transitionMessageAlert";
import { decodeZilPayError } from "./decodeMessage";
const createUserTransition = async (
contract: any,
zilPay: any,
name: string | undefined,
role: string
) => {
try {
const callTransition = await contract.call(
"create_user",
[
{
vname: "name",
type: "String",
value: name,
},
{
vname: "role",
type: "Uint32",
value: role,
},
],
getCallParameters(zilPay)
);
transitionMessageAlert(zilPay, callTransition.ID, "Creating user");
} catch (error) {
toast.error(decodeZilPayError(error));
}
};
export default createUserTransition;

/src/functions/createUserTransition.ts