Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Events docs file = docs/src/pages/docs/events.md #2008

Closed
wants to merge 11 commits into from
4 changes: 4 additions & 0 deletions docs/src/pages/_app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const navigation = [
title: 'Program Derived Addresses',
href: '/docs/pdas',
},
{
title: 'Events',
href: '/docs/events',
},
],
},
{
Expand Down
208 changes: 208 additions & 0 deletions docs/src/pages/docs/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
---
title: Events
description: Anchor - Events
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved
---

Events are an incredibly powerful feature in Anchor. Rather than polling or refreshing to
find out if an account's state has changed, events are like callbacks that save space, compute,
and enable asynchronous programming.

The main downside to events is that they are base64 encoded, and therefore not human readable.
Despite this tradeoff, the UI can easily decode them, and the user benefits from this
compact mode of logging. There are many use cases for events, ranging from simple return values, to compact storage storage solutions for data logging.

## Event Use Cases

### Return Values
We can use an event to immediately provide a return value, such as details related
to a transaction. Since calls to certain programs only return a tx hash, this may not provide sufficient detail to your UI. An event here can provide a richer experience, by returning values about how a particular transaction was executed, for example.

### Callbacks
You may want to monitor an event and be notified only once a transaction is confirmed.
Instead of polling / querying the blockchain and looking for an account's values to update, we can build our client to only act when the program has emitted an event, saving on compute resources and fees.

### Compact Storage
Since events are stored in base64, they can be an economical way to store data
without the trouble of account creation and provisioning for rent-exemption, for example.

## Example

The simplest way to use events is to tack them onto the tail of your program's functions, allowing them to emit
structured data when the function completes without error.

Start by creating a new anchor project:
```bash
$ anchor init Events
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved
```

Next, we will modify the program in `src/lib.rs` to add an event, and the accompanying data to the `initialize`
function.
```rust
// ...
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent {
data: 5,
label: "hello".to_string(),
});
Ok(())
}

// location: src/lib.rs
// ...
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved
#[event]
pub struct MyEvent {
pub data: u64,
#[index]
pub label: String,
}

```

Observe that we are using the event to provide a return value to a successful call to `initialize`.
The data returned is fairly trivial, a `u64` integer and a short `String`. In the example we have only provided static values, but when enriched with dynamic data--such as an operating code and a description of it--the user gets output that can be actionable. Without the event, we would have no specific information about the outcome of the initialize function, besides that it was successful.

Note that the event is declared with the `#[event]` macro, and defined using a `struct`. To supply the values,
the pattern `emit!(MyEvent {...});` is used. The code above is a basic stamp that can be used for creating and consuming events within your program.
Comment on lines +64 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you speak to the #[index] in the event?
I'm relatively new to Anchor, so I could easily be missing something here. But I think if I'm confused, another reader could be as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly don't know what that macro does. I googled it and came up empty-handed. Maybe someone can make a suggestion here, if they think it's very important.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like index will add metadata to the IDL

for off chain indexers


Next, we will add a new function called `test_event` with a new event that emits some other values. Complete code for `src/lib.rs` is shown below:

```rust
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod events {
use super::*;

pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent {
data: 5,
label: "hello".to_string(),
});
Ok(())
}

pub fn test_event(_ctx: Context<TestEvent>) -> Result<()> {
emit!(MyOtherEvent {
data: 6,
label: "bye".to_string(),
});
Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize {}

#[derive(Accounts)]
pub struct TestEvent {}

#[event]
pub struct MyEvent {
pub data: u64,
#[index]
pub label: String,
}

#[event]
pub struct MyOtherEvent {
pub data: u64,
#[index]
pub label: String,
}
```

## Consuming Events

To test our program, we'll build a test client that will be able to consume the events. The structure for doing this is essentially:
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved
```javascript
// ...
let listener = null;

let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
// ...
```

We set up a `listener` that will catch when the event is emitted by the program. Since it's an asynchronous activity, we must include `await` to receive our return values. We call the first function using `program.rpc.initialize()` and when the event happens, we destructure the output into variables `event` and `slot`. Finally, when it's done, we close the listener. We can access our output values as follows: `event.data.toNumber()` or `event.label`.

With the explanation out of the way, edit the file in `tests/events.ts`. Copy-paste the test code into the file as follows, then run `$ anchor test` from the command line to verify that it's working as intended.
ashpoolin marked this conversation as resolved.
Show resolved Hide resolved

```javascript
import * as anchor from "@project-serum/anchor";
// import { Program } from "@project-serum/anchor";
// import { Events } from "../target/types/events";

const anchor = require("@project-serum/anchor");
const { assert } = require("chai");

describe("events", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;

it("Is initialized!", async () => {
let listener = null;

let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);

assert.isAbove(slot, 0);
assert.strictEqual(event.data.toNumber(), 5);
assert.strictEqual(event.label, "hello");
});

it("Multiple events", async () => {
// Sleep so we don't get this transaction has already been processed.
await sleep(2000);

let listenerOne = null;
let listenerTwo = null;

let [eventOne, slotOne] = await new Promise((resolve, _reject) => {
listenerOne = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});

let [eventTwo, slotTwo] = await new Promise((resolve, _reject) => {
listenerTwo = program.addEventListener("MyOtherEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.testEvent();
});

await program.removeEventListener(listenerOne);
await program.removeEventListener(listenerTwo);

assert.isAbove(slotOne, 0);
assert.strictEqual(eventOne.data.toNumber(), 5);
assert.strictEqual(eventOne.label, "hello");

assert.isAbove(slotTwo, 0);
assert.strictEqual(eventTwo.data.toNumber(), 6);
assert.strictEqual(eventTwo.label, "bye");
});
});

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
```

If the event(s) are working correctly, you should have the return values of `5` and `hello` at `EventOne.data` and `EventOne.label`, respectively. The test client verifies the values returned are what is expected using the `assert.strictEqual(...)` expression(s). The same goes for the data in `eventTwo`.


This is the simplest example for creating and consuming events, but the pattern can be duplicated using dynamic data to provide asynchronous, actionable output for your UI or dApps.