📆Calendar

A calendar application using HollowDB.

Perma-Calendar is a demo app for HollowDB. It is client-side only, and every user deploys their contract & their keys. It does not use proofs and only uses whitelisting. The FullCalendar framework is used for calendar rendering. You can check out the live demo here.

Head to the GitHub repo to see the implementation or jump to the Code Snippets section to have a quick look.

The Calendar App

As HollowDB supports both ZK-based inputs and whitelists, it can be used like any other database. This simple example demonstrates how it can be used as a calendar application only using client side code.

Connect Wallet

Thanks to Warp contract's Injected Ethereum Signer, the calendar supports both Arweave wallet and Metamask

Deploy a Contract

As mentioned, for a calendar app every user has to deploy their own HollowDB contract. Users' wallet is added to the whitelist automatically, meaning only they can do write operations. After you connect your wallet, you will see either a Deploy or Redeploy button depending on if a previously deployed contract exists or not.

Add Calendar Events

You can click on any date to create an event. After submitting the event, the user receives a transaction to write the event to the HollowDB contract. After a successful event creation, the event will be shown on the calendar.

Enter Event Name

Sign the Transaction

Ta-da!

Delete Event

To delete an event, simply click on the event and sign the transaction.

Code Snippets

The folder structure of the application is as follows:

. 
├─ src 
│       ├── components (header and layout)
│       ├── constants (source tx id of the contract to be deployed)
│       ├── context (wallet connection and contract deploy logic)
│       ├── pages (home page and react/nextjs defaults)

└── ...

Contract Deployment

// srcTx type is created, it requires srcTxID, deployer address and signer
// signer for metamask is retrieved from InjectedEthereumSigner and window.ethereum
// signer for arweave is retrieved from InjectedArweaveSigner

const srcTx: FromSrcTxContractData = generateContractInfo(
      HOLLOWDB_SRCTXID,
      address,
      userSigner
    );

// HollowDB deployed
const deployTx = await warp.deployFromSourceTx(srcTx);

// HollowDB instance is created using SDK and deploy tx information
const hollowdb = new SDK("use_wallet", deployTx.contractTxId, warp);

// The instance is stored and shared accross the web-app
setHollowdb(hollowdb);

Put Operation

async function put(key: string, value: {}) {
    /* ... some checks ... */
    await hollowdb?.put(key, JSON.stringify(value));
  }

const handleDateSelect = (selectInfo: DateSelectArg) => {
    /* ... some checks ... */

    let title = prompt("Please enter a new title for your event");
    let calendarApi = selectInfo.view.calendar;

    // The event is added to both calendarApi and HollowDB
    if (title) {
      const eventId = createEventId();
      const start = selectInfo.startStr;
      const end = selectInfo.endStr;
      const allDay = selectInfo.allDay;
      calendarApi.addEvent({
        id: eventId,
        title,
        start: start,
        end: end,
        allDay: allDay,
      });
      put(eventId, { title: title, start: start, end: end, allDay: allDay });
    }
  };

Remove Operation

async function remove(key: string) {
    if (!isConnected) {
      return;
    }
    // Empty event object
    // We check for empty events using empty string
    const emptyValue = JSON.stringify({
      title: "",
      start: "",
      end: "",
      allDay: "",
    });
    await hollowdb?.update(key, emptyValue);
  }

const handleEventClick = (clickInfo: EventClickArg) => {
    /* ... some checks ... */
    
    // The event is removed from both calendarApi and HollowDB
    remove(clickInfo.event.id);
    clickInfo.event.remove();
    
  };

Handling Previously Deployed Contracts

const checkPrevEvents = async () => {
      if (hollowdb?.contractTxId == "" || !isConnected) return;

      // clean up the calendar (helps with redeployment)
      removeAll();

      let oldEventKeys: Array<string> = [];
      let oldEvents: Array<any> = [];

      // get all existing keys on the hollowdb contract
      await hollowdb?.getAllKeys().then((keys: []) => {
        if (keys) {
          oldEventKeys = Array.from(keys.values());
        }
      });

      // get all existing values on the hollowdb contract then convert it to an array
      const eventValues = await hollowdb?.getStorageValues(oldEventKeys);
      const mappedEvents = eventValues?.cachedValue;
      if (mappedEvents) {
        oldEvents = Array.from(mappedEvents.values());
      }

      // filter empty events
      oldEvents = oldEvents.filter((elements) => {
        return elements !== null;
      });

      // Obtain a calendar api instance to add previous events to the calendar
      const calendarApi = CalendarRef.current.getApi();
      for (let i = 0; i < oldEvents.length; i++) {
        oldEvents[i] = await JSON.parse(oldEvents[i]);
        const eventId = createEventId();
        if (oldEvents[i].title != "")
          calendarApi.addEvent({
            id: eventId,
            title: oldEvents[i].title,
            start: new Date(oldEvents[i].start),
            end: new Date(oldEvents[i].end),
            allDay: new Date(oldEvents[i].allDay),
          });
      }
    };

Congrats! You have successfully learned how to deploy and use an HollowDB contract.

Last updated