Skip to main content

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:

  • Nat to represent Michelson's natural integer type nat of arbitrary precision
  • Bytesto represent bytes values

It is convienient to:

  • downcast Nat values to native TypeScript values typed number
  • turn Hex-encoded IPFS hash to poll definition's fields (utterance and choices)

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:

  1. loads data from contract
  2. maps each poll data to the Poll Typescript
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:

  • contrat object has been locally obtained with hook useContract from PollContract.tsx
  • async method get_poll is used to retrieve poll container
  • Nat's to_number method is used to downcast to native type number
  • Bytes's hex_decode method 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:

  • contract object has been locally obtained with hook useContract from PollContract.tsx
  • poll identifier selected and answer identifier choice are upcasted to Nat type as specified by contract's entry respond
  • last argument {} of respond method 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>
  • contract object has been locally obtained with hook useContract from PollContract.tsx
  • poll's definition IPFS hash is converted to bytes with Bytes utility method hex_encode, as specified by the contract's entry add_poll
  • last argument {} of respond method 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:

  • useEffect React hook is invoked after UI component is loaded
  • contract object has been locally obtained with hook useContract from PollContract.tsx
  • call parameter provides the as field to set the source value 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_NewPoll and register_Approve are binder's event handler registration methods
  • run_listener is the function to start the event listener process (provided by @completium/event-listener package)