-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.html
752 lines (615 loc) · 27.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<meta name="Description" content="Put your description here." />
<base href="/" />
<script type="module">
import "reveal.js/dist/reveal.css";
import "reveal.js/dist/theme/black.css";
import "reveal.js/plugin/highlight/monokai.css";
import "@mythosthesia/reveal-course-preset/styles.css";
</script>
<title>Holochain Lesson 2</title>
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>HDK: basic functions</h1>
</section>
<!-- prettier-ignore -->
<section language="markdown" animate="by-line with-ancestry">
### HDK & HDI
- Holochain Development Kit (HDK)
- To create a coordinator zome, create a rust crate that depends on the HDK and compile it
- Allows to define zome functions that can be called from outside the
zome
- Docs at https://docs.rs/hdk
- Holochain Deterministic Integrity (HDI)
- Subset of the HDK to build integrity zomes
- Only types that deal with validation rules and entry definitions
- Docs at https://docs.rs/hdi
</section>
<section >
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Defining Zome Functions: Exposure
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre
class="fragment fade-in"
><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
use hdk::prelude::*;
#[hdk_extern] // - Comes from hdk::prelude::*
// - Exposes a function to be callable from the outside
fn zome_function_hello(name: String) -> ExternResult<String> { // - Zome function name
// must be unique in this zome
// - Zome functions can only
// accept one parameter
Ok(format!("hello {}!", name)) // Must return an "ExternResult" of a typing that implements
// "Serialize", "Deserialize" and "Debug"
}
#[hdk_extern]
fn zome_function_hello_world(_: ()) -> ExternResult<String> { // To indicate disregard
// for input parameter, we use
// the unit typing "()"
Ok(format!("hello world!"))
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Defining Zome Functions: Custom Inputs
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre
class="fragment fade-in"
><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
use hdk::prelude::*;
// We can define custom input parameter structs
#[derive(Serialize, Deserialize, Debug)] // Input parameters must derive
// "Serialize", "Deserialize" and "Debug"
struct CustomInputStruct {
name: String
}
#[hdk_extern]
fn zome_function_with_custom_input(input: CustomInputStruct) -> ExternResult<String> { // Ok
Ok(format!("hello {}!", input.name))
}
struct BrokenInputStruct { // This input struct won't work,
// because it doesn't derive the right traits
name: String
}
#[hdk_extern]
fn zome_function_with_broken_custom_input(
input: BrokenInputStruct // Error: Input parameters must derive
// "Serialize", "Deserialize" and "Debug"
) -> ExternResult<String> {
Ok(format!("hello {}!", input.name))
}
</code
></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Defining Zome Functions: Capturing Debugging Data
- You'll need to use the `wasm_error!()` macro to return errors.
- You'll need to use `.map_err(|err| wasm_error!(err))` for some HDK function calls.
- You can use `error!()` or `warn!()` in your zome functions to log to the terminal console.
</fragment>
<!-- prettier-ignore -->
<fragment >
<pre
class="fragment fade-in"
><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
use hdk::prelude::*;
#[hdk_extern]
fn returning_errors(_: ()) -> ExternResult<()> {
Err(wasm_error!( // "wasm_error!" Captures debugging information (eg. line numbers)
WasmErrorInner::Guest(String::from("This function returns an error"))
))
}
#[hdk_extern]
fn debugging(_: ()) -> ExternResult<()> {
error!("This is an error log that functions just like println");
warn!("This is a warn log that functions just like println");
Ok(())
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Retrieving Static Cell Information (I)
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre
class="fragment fade-in"
><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
use hdk::prelude::*;
#[hdk_extern]
fn get_my_pub_key(_: ()) -> ExternResult<AgentPubKey> { // "AgentPubKey" acts as the "agent ID"
let my_agent_info = agent_info()?; // Will return a different value depending on
// which cell is executing the function
let my_pub_key: AgentPubKey = my_agent_info.agent_initial_pubkey; // In the future the
// public key of a cell
// may change
Ok(my_pub_key)
}
#[hdk_extern]
fn get_zome_name(_: ()) -> ExternResult<ZomeName> { // "ZomeName" is a simple Tuple(Struct)
// wrapping a String
let my_zome_info = zome_info()?; // Will return a different value depending on
// which zome this function is defined within,
// but will return the same for all agents
let zome_name: ZomeName = my_zome_info.name; // Human readable name,
// defined in the DNA manifest
let zome_id: ZomeId = my_zome_info.id; // Zome index (integer) in the DNA that Holochain
// uses to identify the zome
Ok(zome_name)
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Retrieving Static Cell Information (II)
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre
class="fragment fade-in"
><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
use hdk::prelude::*;
#[hdk_extern]
fn get_dna_hash(_: ()) -> ExternResult<DnaHash> {
let my_dna_info = dna_info()?; // Will return the same value for all agents
// and zomes in the same DNA
let dna_name: String = my_dna_info.name; // Human readable name, defined in the DNA manifest
let dna_hash: DnaHash = my_dna_info.hash; // Hash of source code, network seed and properties
let dna_properties = my_dna_info.properties; // Properties: configuration that the
// DNA can read to modify it's behaviour
let pubkey = AgentPubKey::try_from(dna_properties)?; // 'dna_properties' is of type
// 'SerializedBytes', so it can
// serialize to any rust typing
// that derives 'Serialize' and
// 'Deserialize'
Ok(dna_hash)
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Defining An Entry Type
- Defines the format for a particular type of entry
- Can only be done in an integrity zome
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/integrity/lib.rs
use hdi::prelude::*;
#[hdk_entry_helper] // Adds necessary trait implementations
struct Comment {
comment: String
}
#[hdk_entry_helper]
struct Post {
title: String,
content: String
}
#[hdk_entry_defs] // - Defines all the entries for this zome
// - Must be the only "#[hdk_entry_defs]" in the zome
// - Can only be defined in integrity zomes
#[unit_enum(UnitEntryTypes)] // Defines a "UnitEntryTypes" enum that's a copy of "EntryTypes"
// but with the converted variants to unit variants (no payload)
enum EntryTypes { // Enum with each entry type as a variant
#[entry_def( // Configures entry behaviour
name = "comment", // Must be unique across entry types
visibility = "private" // Entry not published to the DHT, only stored in the source chain
)]
Comment(Comment),
// Can be omitted, defaults to the struct name in snake_case and "public"
Post(Post),
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions
#### Overview
- These are actions you can take on your Holochain app's state machine, stored in the app's DHT
- Includes actions like create, update, delete
- Can be done in an integrity zome or in a coordinator zome
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions
#### create_entry()
- Issues a notice to the DHT to record the creation of a new entry node for some given type
- Records both the details of the entry itself, and a create action, which describes a claim to having "created" that entry
- In the merkle tree, a create action branches directly from an entry itself
- If the submitted entry already can already be found in the DHT, then only the create action is added (since there can be no duplicates in a content-addressed DHT)
- There can be any number of create actions added to the DHT, all claiming to have created the same entry
- The create actions may contradict one another (for example, by claiming different authors of the entry)
- It's up to the application code to interpret the full set of discoverable actions, and determine a single, well-believed world state
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions
#### create_entry()
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[hdk_extern]
fn create_comment(comment: Comment) -> ExternResult<()> {
create_entry(EntryTypes::Comment(comment))?; // - "create_entry" comes from hdk::prelude::*;
// - Input must be an instance of the
// entry types enumerable
Ok(())
}
#[hdk_extern]
fn create_comment_and_return_action_hash(comment: Comment) -> ExternResult<ActionHash> {
let action_hash = create_entry(EntryTypes::Comment(comment))?; // create_entry() returns
// the hash of the action
// that has just been inserted
// into the agent's
// source chain
Ok(action_hash)
}
</code
></pre>
</fragment>
</section>
<section >
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions
#### update_entry()
- Records a modification submitted on an entry, downstream of that entry's initial create action
- An update action must be issued as a child of some other action
- Can be the child of either a create action, or of another pre-existing update action
- After adding an update, the original entry will still exist in the DHT
- And so will all of that entry's upstream create and update actions
- One create or update action may have any number of child update actions added under it
- In this way, a set of update actions can form a merkle chain of compatible updates to one entry
- Furthermore, a set of update actions might branch to form a tree of incompatible update chains
- It's up to the application code to interpret the full set of discoverable actions, and determine a single well-believed world state
- An update action cannot branch directly from an entry itself
- This is because, for any given entry, there may be several valid create and update actions all descended from that one entry
- Attempting to attach an update directly to an entry would require deciding which of the valid action chains you wanted to append the new update action to
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions
#### update_entry()
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[derive(Serialize, Deserialize, Debug)]
struct UpdateCommentInput {
original_action_hash: ActionHash,
new_comment: Comment
}
#[hdk_extern]
fn update_comment(input: UpdateCommentInput) -> ExternResult<ActionHash> {
let action_hash = update_entry(
input.original_action_hash, // The original action must be a create or an update action
EntryTypes::Comment(input.new_comment) // New entry must be of the same entrytype
)?;
Ok(action_hash)
}
</code
></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Actions</h3>
#### delete_entry()
- Must be scoped to an action, not an entry
- Deleted entry and action will still exist in the DHT after deleting
- We are just marking them as "deleted"
- One action may be deleted multiple times
- It's up to the application to interpret what "deleting an action" means
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[hdk_extern]
fn delete_comment(comment_action_hash: ActionHash) -> ExternResult<()> {
let action_hash = delete_entry(comment_action_hash)?; // - Deleting an entry is also its
// own action
// - Deleted action hash must be
// a create or an update action
Ok(())
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Retrievals
#### Overview
- Read requests issued to the DHT
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Retrievals
#### get()
- Given an entry hash or an action hash, return the "Record" that hashes to that hash
- Record is the union of the action and optionally its accompanying entry
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre>
<code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
><!-- TODO: what about get with entryhash, which record would you get back? -->
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[hdk_extern]
fn get_comment(comment_action_hash: ActionHash) -> ExternResult<Option<Record>> {
let comment_record: Option<Record> = get(
comment_action_hash, // Accepts an "ActionHash" or an "EntryHash"
GetOptions::default() // Cache control, by default returns cached records
)?;
/* In case you need to extract the entry struct from the record */
let maybe_entry: Option<Entry> = record
.entry
.into_option();
let entry: Entry = maybe_entry.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("This record doesn't include any entry"))
))?;
let _comment = Comment::try_from(entry)?;
Ok(comment_record)
}
</code
>
</pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Retrievals
#### get_details() (I)
- Given an action hash, return the action, its associated entry, and all the actions that refer to that action
<!-- TODO: what do we mean by details? All downstream actions from the given action -->
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[hdk_extern]
fn get_comment_action_details(comment_action_hash: ActionHash) -> ExternResult<Record> {
let record_details: Option<Details> = get_details(
comment_action_hash, // Accepts an "ActionHash" or an "EntryHash"
GetOptions::default() // Same options as "get()"
)?;
match record_details {
Some(Details::Record(RecordDetails { record, deletes, updates, .. })) => Ok(record),
_ => Err(wasm_error!(
WasmErrorInner::Guest(String::from("Error trying to get the details of this action"))
))
}
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Entry Retrievals
#### get_details() (II)
- Given an entry hash, return the entry and all the actions that refer to that entry
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
#[hdk_extern]
fn get_comment_entry_details(comment_entry_hash: EntryHash) -> ExternResult<Entry> {
let entry_details: Option<Details> = get_details(
comment_entry_hash, // Entry Hash
GetOptions::default()
)?;
match entry_details {
Some(Details::Entry(EntryDetails { entry, actions, deletes, updates, .. })) => Ok(entry),
_ => Err(wasm_error!(
WasmErrorInner::Guest(String::from("Error trying to get the details of this action"))
))
}
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Links
- Linking from one hash to another, with some optional metadata
- Link components:
- Base hash - can be:
- public key
- entry hash
- action hash
- external hash (doesn't exist in this DHT)
- Target hash: can be of the same types as the base hash
- Type: app defined link type
- Tag: app defined arbitrary metadata
- Neither the base nor the target hash need to exist as actions or entries in the DHT
- Defining link types
- Only in integrity zomes
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/integrity/lib.rs
use hdi::prelude::*;
// Defining link types
#[hdk_link_types] // - Defines all the link types for this zome
// - Must be the only "#[hdk_link_types]" in the zome
// - Can only be defined in integrity zomes
enum LinkTypes { // Enum with each entry type as a variant
AuthorToComment, // Must be a Unit variant (without any payload)
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Link Actions
#### create_link()
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{LinkTypes, EntryTypes, Comment}; // Import the types defined
// in our integrity zome
#[hdk_extern]
fn create_comment(comment: Comment) -> ExternResult<ActionHash> {
let comment_action_hash = create_entry(EntryTypes::Comment(comment))?; // Create comment
let my_pub_key: AgentPubKey = agent_info()?.agent_initial_pubkey; // The author of the
// comment is the agent
// executing this
// function call
let create_link_action_hash: ActionHash = create_link( // Creating a link is
// an action in itself
my_pub_key, // Base hash: my agent pub key
comment_action_hash, // Target hash: the action hash that has just been created
LinkTypes::AuthorToComment, // LinkType: one of the variants defined in the integrity zome
() // Link tag, can be any struct that derives 'Serialized' and 'Deserialized'
)?;
Ok(comment_action_hash)
}
</code></pre>
</fragment>
</section>
<section>
<!-- prettier-ignore -->
<fragment language="markdown" animate="by-line with-ancestry">
### Link Retrievals
#### get_links()
- Links are attached to the base hash
- You can retrieve all the links attached to a given hash as a base
</fragment>
<!-- prettier-ignore -->
<fragment>
<pre><code class="rust" data-noescape data-trim
animate="by-line with-ancestry balanced separate-comments trailing-comments-in-popover strike-on-error-in-comment"
>
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::*; // Import the types defined in our integrity zome
#[hdk_extern]
fn get_all_comments_by_agent(author: AgentPubKey) -> ExternResult<Vec<Record>> {
let links: Vec<Link> = get_links(
author, // Base hash: the given agent pub key
LinkTypes::AuthorToComment, // LinkType: one of the variants defined in the integrity zome
None, // "Option<LinkTag>": filter result set down to only those links
// with a matching prefix on their link tag
)?;
let mut comments: Vec<Record> = vec![];
for link in links {
if let Some(record) = get(
ActionHash::from(link.target), // - Conversion is important to specify the
// type of hash we are retrieving for
// - Will be the action hash for each of the
// comments that the agent has created
GetOptions::default()
)? {
comments.push(record);
}
}
Ok(comments)
}
</code></pre>
</fragment>
</section>
<section>
<h1>That's it!</h1>
</section>
</div>
</div>
<script type="module">
import Reveal from "reveal.js";
import { plugins, config } from "@mythosthesia/reveal-course-preset";
let deck = new Reveal();
deck.initialize(config);
</script>
</body>
</html>