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.
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;