Comment on page

Adding the UI

Last but not least, we'll want to have a user interface for our token.
We'll look at a prebuilt template, focusing on how everything fits together and how to wire everything together, rather than on actual advanced tutorials on Web3 UI development. The template includes the code for the Wrapped ERC20 contract we wrote in the previous sections and is based off of the Vue + fhevmjs template. We use Typescript throughout the example.
You can find the code for this section in our github repo.

Starting From a Template

First, start by cloning the example repository:
git clone https://github.com/FhenixProtocol/werc20-ui-example

Install Dependencies

cd werc20-ui-example && pnpm install

Get Contracts

Now, let's bring in our contracts. We're using git submodules to link the repo containing our contracts and our UI:
pnpm get:contracts
There are many ways to organize contracts and UI - this is just one pattern, so adapt to what you know & like and don't be afraid to experiment!
Now you should see the contracts folder populated with all the good stuff we created earlier. Feel free to look around and make sure the contract code is updated. If you want to make any changes to the contract code, go ahead.

Compiling Contracts

At the moment our contracts only exist as solidity files. Let's fix that -
pnpm build:contracts
This will trigger both solidity compilation & building the contracts into typescript files.

Deploying Contracts

We can also deploy our contract. If LocalFhenix isn't running, you can start it using pnpm start:localfhenix from the contracts folder.
cd contracts
pnpm deploy:contracts --network localfhenix
Seeing an error? you might be missing tokens for your deployer address. In this case try pnpm faucet to get some tokens for localfhenix.

Connecting the Frontend

Let's look at why all this is useful to do together with our frontend code. Everything we'll look at is in App.vue.
First, we initialize fhevmjs:
export default defineComponent({
name: 'App',
...
setup() {
const instance = ref<FhevmInstance | undefined>(undefined);
return {instance};
},
mounted() {
let self = this;
const initInstance = async () => {
await initFhevm();
const chainIdHex = await window.ethereum.request({method: 'eth_chainId'});
const thisProvider = new ethers.BrowserProvider(window.ethereum)
let networkPublicKey = localStorage.getItem('fhepubkey');
if (!networkPublicKey || networkPublicKey === "0x") {
publicKey = await thisProvider.call({from: null, to: '0x0000000000000000000000000000000000000044'});
if (publicKey) {
// cache global public key - should change it to be per chain-id
localStorage.setItem('fhepubkey', networkPublicKey);
}
}
const chainId = parseInt(chainIdHex, 16);
return createInstance({chainId, publicKey: networkPublicKey});
};
initInstance().then(
(instance) => {
this.loading = false;
this.instance = instance;
this.refreshBalances();
}
);
},
...
});
Now, interacting with encrypted data in our contracts is pretty straightforward if you've seen EVM Web3 contracts in action - we can simply import the types & deployment data generated by our contracts and use them directly:
// can use this, hard code the address or any other way to map the contract address
import DeployedContract from "../contracts/deployments/localfhenix/WrappingERC20.json";
import {WrappingERC20__factory} from "../contracts/types";
async getEncryptedBalance(): Promise<number> {
const thisProvider = new ethers.BrowserProvider(window.ethereum)
let signer = await thisProvider.getSigner();
let contractAddress = DeployedContract.address;
// ts-ignore because different ethers versions can cause typescript to think
// there's a type mismatch
// @ts-ignore
const werc20 = WrappingERC20__factory.connect(contractAddress, signer)
if (this.instance) {
// this is an ehpemeral key used to query encrypted data for the user
// NOT the global network public key from the init
// Here we don't really care about the EIP-712 token & signature, we just want to
// use the public key associated with it
let txPublicKey = this.instance.getTokenSignature(contractAddress)?.publicKey;
if (!txPublicKey) {
txPublicKey = await this.instance.generateToken({verifyingContract: contractAddress}).publicKey;
}
try {
let encBalance = await werc20.balanceOfEncrypted(txPublicKey);
// instance.decrypt uses the txPublicKey which is loaded internally
// which is why we don't explicitly provide it
this.encryptedBalance = this.instance.decrypt(contractAddress, encBalance);
} catch (e) {
// 0 balance will error here
}
}
return 0
}
Sending a transaction is even easier, since there's less fiddling with keys
async sendToContract (input: number) {
if (!this.instance) {
alert("fhe not initialized");
return;
}
const thisProvider = new ethers.BrowserProvider(window.ethereum)
let signer = await thisProvider.getSigner();
try {
// @ts-ignore
const werc20 = WrappingERC20__factory.connect(DeployedContract.address, signer)
let encToSend = await this.instance.encrypt32(input);
// for example purposes just send to the contract
await werc20.transferEncrypted(DeployedContract.address, encToSend);
} catch (e) {
alert(`error: ${e}`)
}
this.refreshBalances();
},

Okay, Let's Run this thing!

pnpm serve
In the unlikely scenario that everything worked up to this point you should now see:
DONE Compiled successfully in 1657ms 1:48:47 PM
App running at:
- Local: http://localhost:8082/
- Network: http://172.21.20.133:8082/
Note that the development build is not optimized.
To create a production build, run pnpm run build.
No issues found.
GG WP
🎉
🎉
🎉
🎉

Side Note: Why Go Through all this?

You can just copy over the compiled .json files from your contracts, hard code deployed addresses (or pass them through environment variables) and be done with it! Yep, that can be a quick-and-dirty solution. However, the more complex the project the more challenging it becomes to cleanly integrate contracts and user interfaces. Frequent changes, debugging and coordinating multiple people are all challenges - but even for personal development, I just like having my UI aware of the contract interfaces and provide typing hints.