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 typenat
of arbitrary precisionBytes
to represent bytes values
It is convienient to:
- downcast
Nat
values to native TypeScript values typednumber
- turn Hex-encoded IPFS hash to poll definition's fields (
utterance
andchoices
)
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
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 hookuseContract
fromPollContract.tsx
- async method
get_poll
is used to retrieve poll container Nat
'sto_number
method is used to downcast to native typenumber
Bytes
'shex_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 hookuseContract
fromPollContract.tsx
- poll identifier
selected
and answer identifierchoice
are upcasted toNat
type as specified by contract's entryrespond
- last argument
{}
ofrespond
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 hookuseContract
fromPollContract.tsx
- poll's definition IPFS hash is converted to bytes with
Bytes
utility methodhex_encode
, as specified by the contract's entryadd_poll
- last argument
{}
ofrespond
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 loadedcontract
object has been locally obtained with hookuseContract
fromPollContract.tsx
- call parameter provides the
as
field to set thesource
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
andregister_Approve
are binder's event handler registration methodsrun_listener
is the function to start the event listener process (provided by@completium/event-listener
package)