How to emit and capture events in Phat Contract

2024-01-22

Events and logs are important to the Web3 developers because they facilitate communication between smart contracts and their user interfaces. Like other smart contracts, Phat Contract can emit events to communicate that something has happened. Unlike other smart contracts, Phat Contract events are only available inside the cluster. You need to use our JS-SDK to capture these events and take actions when they happen.

Here is an example of an ERC-20 like implementation from ink-examples, and it can deployed on Phala Network. You can test it on the PoC6 Testnet.

The complete code example can be found here: https://github.com/Leechael/phat-contract-events-example/

How to Emit Events from a Phat Contract

Firstly, you need to declare an event definition

#[ink(event)]
pub struct Transfer {
    #[ink(topic)]
    from: Option<AccountId>,
    #[ink(topic)]
    to: Option<AccountId>,
    value: Balance,
}

To define an Ink! event, you need to add the #[ink(event)] macro to the struct definition. Also, apply the #[ink(topic)] attribute tag to each item in your event that you want to mark as 'should be indexed'. Note that you can index a maximum of four attributes.

Emitting Events in a Constructor

You can use Self::env().emit_event() to emit events in a constructor function:

#[ink(constructor)]
pub fn new(initial_supply: Balance) -> Self {
    let caller = Self::env().caller();
    let mut balances = Mapping::default();
    balances.insert(caller, &initial_supply);

    Self::env().emit_event(Transfer {
        from: None,
        to: Some(caller),
        value: initial_supply,
    });

    Self {
        total_supply: initial_supply,
        balances,
        allowances: Mapping::default(),
    }
}

Emitting Events in a Message

You can use self.env().emit_event() to emit events in a message function with &mut mark:

self.env().emit_event(Transfer {
    from: Some(*from),
    to: Some(*to),
    value,
});
💡
Events can only be emitted from transactions. It is not possible to emit an event from an off-chain message, which means the &mut self mark MUST be used.

Capture Events with JS-SDK

Once we have the Phat Contract instance that emit events, we can capture events in a web3 application with our JS-SDK.

You need to load the ABI JSON first:

const fs = require('fs')
const abi = fs.readFileSync('./target/ink/erc20.json', 'utf8')

Since the events only occur within the cluster and are not broadcasted on-chain, you can use the logger to capture them:

const ws = argv['--ws'] || process.env.WS || 'wss://poc6.phala.network/ws'
const logger = await getLogger({ transport: ws })

// Or you already use `getClient` before:
const client = await getClient({ transport: ws })
const 

The logger object allows you to access the log stream from a specified PRuntime Node. Now you can call logger.tail(1000) to fetch the last 1000 log entries. For further usage, you can learn more about that via the JS-SDK documentation and the tail.js example.

Currently, if you want capture the event after submitting the transaction, you need a bit more boilerplate codes:

const { blockNumber } = await client.phactory.getInfo({})

const result = await contract.send.transfer(
  { cert, address: cert.address, pair },
  toAddress,
  value
)
await result.waitFinalized()

const { records } = await logger.tail(10000, {
  contract: contract.address.toHex(),
  abi,
  type: ['Event'] as LogTypeLiteral[],
})
const matched = records.filter(i => (i as SerMessageEventWithDecoded).blockNumber >= blockNumber)
console.assert(matched.length === 1, 'It should only one matched event.')
for (const record of matched) {
  let rec = record as SerMessageEventWithDecoded
  prettyPrint(rec)
}

The SerMessageEventWithDecoded struct represents an event that includes decoded data. It has the following structure:

interface SerMessageEventWithDecoded {
  type: 'Event'
  sequence: number
  blockNumber: number
  contract: string
  topics: string[]
  payload: string
  decoded?: {
		args: Codec[];
	  event: {
		  args: {
			  name: string;
			  type: TypeDef;
			}[];
		  docs: string[];
		  fromU8a: (data: Uint8Array) => DecodedEvent;
		  identifier: string;
		  index: number;
		}
	}
}

Conclusion

Events are a crucial aspect of Phat Contract development as they allow you to determine the success or failure of your transaction and provide valuable insights into cluster activity. It is important to familiarize yourself with the pattern of events in order to effectively utilize them.

About Phala

Phala Network is a decentralized cloud that offers secure and scalable computing for Web3.

With Phat Contracts, an innovative programming model enabling trustless off-chain computation, developers can create new Web3 use cases.

Get the latest Phala Content Straight To Your Inbox.