Contract Integration
This section presents in detail the interaction between the DApp's UI and the poll contract:
- how polls data are retrieved from contract?
- how entries and views are invoked?
Generate bindings
The following Completium CLI command generates the contract(s) bindings for a DApp:
completium-cli generate binding-dapp-ts ./poll-contract/contracts/poll.arl > ./src/bindings/poll.ts
By default, the originate method is not generated in the DApp bindings (to reduce binding size). If the Dapp is required to originate the contract, use the --with-dapp-originate argument:
completium-cli generate binding-dapp-ts --with-dapp-originate ./poll-contract/contracts/poll.arl > ./src/bindings/poll.ts
Retrieving poll data
The Typescript binding provides the method get_poll that returns the poll container as a list of pair of poll key and poll_value, whose type finally reduces to:
Array<[ Nat, {
  ipfs_hash : Bytes,
  responses : Array<[ Nat, Nat ]>,
  creation  : Date
}]>;
The binding is using types from @completium/archtype-ts-types package:
- Natto represent Michelson's natural integer type- natof arbitrary precision
- Bytesto represent bytes values
It is convienient to:
- downcast Natvalues to native TypeScript values typednumber
- turn Hex-encoded IPFS hash to poll definition's fields (utteranceandchoices)
A dedicated type Poll is created to merge contract's polls data with IPFS stored poll definition:
export interface Poll {
  id        : number,
  utterance : string,
  img       : string,
  choices   : Array<string>
  creation  : Date,
  responses : Array<[ number, number]>
}
The load_data function from Polls.tsx:
- loads data from contract
- maps each poll data to the PollTypescript
const loadData = async () => {
  const poll_data = await contract.get_poll()
  const polls     = await Promise.all(poll_data.map(async ([poll_id, poll_value]) => {
    const url = ipfs + poll_value.ipfs_hash.hex_decode()
    const res = await fetch(url)
    const ui : UIPoll = await res.json()
    return {
      id        : poll_id.to_number(),
      utterance : ui.utterance,
      img       : ui.img,
      choices   : ui.choices,
      creation  : poll_value.creation,
      responses : poll_value.responses.map(x => [ x[0].to_number(), x[1].to_number() ])
    }
  }))
  setPolls(polls.sort((p1,p2) => p1.creation.getTime() - p2.creation.getTime()))
}
Comments:
- contratobject has been locally obtained with hook- useContractfrom- PollContract.tsx
- async method get_pollis used to retrieve poll container
- Nat's- to_numbermethod is used to downcast to native type- number
- Bytes's- hex_decodemethod is used to decode the IPFS hash
- polls are sorted by creation date to present more recent polls first
Invoking respond entry
RespondPoll UI component displays the SUBMIT button that calls contract's respond entry point.
The code that handles the click button event is:
const respond = async () => {
  try {
    if (choice !== undefined) {
      await contract.respond(new Nat(selected), new Nat(choice), {})
    }
  } catch(e) {
    console.log(e)
  }
}
<Button onClick={respond}>submit</Button>
Comments:
- contractobject has been locally obtained with hook- useContractfrom- PollContract.tsx
- poll identifier selectedand answer identifierchoiceare upcasted toNattype as specified by contract's entryrespond
- last argument {}ofrespondmethod is the call's optional parameters (typically the amount of tez to send the contract)
Invoking add_poll entry
AddPoll UI component displays the SUBMIT button that calls contract's add_poll entry point.
The code that handles the click button event is:
const add_poll = async () => {
  try {
    await contract.add_poll(Bytes.hex_encode(uri), {})
  } catch (e) {
    console.log(e)
  }
}
<Button onClick={add_poll}>submit</Button>
- contractobject has been locally obtained with hook- useContractfrom- PollContract.tsx
- poll's definition IPFS hash is converted to bytes with Bytesutility methodhex_encode, as specified by the contract's entryadd_poll
- last argument {}ofrespondmethod is the call's optional parameters (typically the amount of tez to send the contract)
Invoking already_responded view
When loaded, RespondPoll UI component checks whether wallet address has already responded to the poll by invoking already_responded view. If so, poll statistics are displayed:
const RespondPoll = () => {
  useEffect(() => {
    const responded = await contract.view_already_responded(
      new Nat(poll.id),
      { as : new Address(wallet_address) }
    )
    if (responded) {
      await loadResponses(poll.id)
    }
  }, [])
}
Comments:
- useEffectReact hook is invoked after UI component is loaded
- contractobject has been locally obtained with hook- useContractfrom- PollContract.tsx
- call parameter provides the asfield to set thesourcevalue used by the view
Listening to events
The poll contracts emits events on poll addition/approval and when a response is submitted. When an event is emitted, the DApp notifies the user with a snack message and a notification appears in the events' panel.
Contract's binding offers methods to register event's handlers. An event handler is a function called when an event is emitted, and that takes this new event as argument. It takes a second optional argument that provides blockchain-related information:
- emitter contract address
- block hash
- operation hash
- operation timestamp
- event name
Event handlers are registered in the useEffect hook of constate Events component:
useEffect(() => {
  const startListener = async () => {
    contract.register_Response(async (e : Response, d ?: EventData) => {
      setAlertMsg(make_response_msg(e))
      setAlerOpen(true)
      await loadResponses(e.poll_id.to_big_number().toNumber())
      if (d) addEvent(d)
    })
    contract.register_NewPoll((np : NewPoll, d ?: EventData) => {
      setAlertMsg(make_new_poll_msg(np))
      setAlerOpen(true)
      if (d) addEvent(d)
    })
    contract.register_ApprovePoll((ap : ApprovePoll, d ?: EventData) => {
      setAlertMsg(make_poll_confirmed_msg(ap))
      setAlerOpen(true)
      if (d) addEvent(d)
    })
    await run_listener({
      endpoint: endpoint,
      verbose: false,
      horizon: 0
    })
  };
  startListener()
}, [])
Comments:
- register_Response,- register_NewPolland- register_Approveare binder's event handler registration methods
- run_listeneris the function to start the event listener process (provided by- @completium/event-listenerpackage)