This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IOTA Notarization enables creation of immutable, on-chain records for arbitrary data by storing it (or a hash) in dedicated Move objects on the IOTA ledger. The workspace has two components: Notarization (creating tamper-proof records) and Audit Trail (structured, role-based audit logging).
- Everything contained in this repository is part of the Notarization Toolkit - do not use synonyms like "Notarization Suite", "Notarization SDK" or similar labels for the Notarization Toolkit.
- The Notarization Toolkit is part of the IOTA Trust Framework
- Use standard capitalization for the word
Toolkit(includes "title case" where needed) - use "Toolkit" or "toolkit" whatever suites into the context the best. In this stylguideToolkitis used for referencing the term. Use "title case" allways forNotarization Toolkit(never useNotarization toolkitornotarization toolkit).
- Use standard capitalization for the word
- The IOTA Trust Framework consist of Trust Framework Products (TF products)
- The Notarization Toolkit contains two TF products: Single Notarization and Audit Trails
- In the context of Notarization Toolkit documentation, Single Notarization and Audit Trails are called components
- In the context of IOTA Trust Framework documentation, Single Notarization and Audit Trails are called TF products
- These rules also apply to future TF products in the Notarization Toolkit (i.e. "Proof of Inclusion")
- Regarding usage of singular and plural in TF product resp. Notarization Toolkit component names:
- If the product is meant itself:
- Use the product name (i.e.
Audit Trails,Notarization) with singular form - example: "Audit Trails is the best ..." - Use capitalization (a.k.a. title case) - examples
Audit Trails,Notarization
- Use the product name (i.e.
- If multiple instances of a product (typically equivalent to multiple on-chain objects) are meant:
- Use plural (i.e.
Using multiple audit trails facilitates ...orAvoid creating too much notarizations for ...) - Use lower case for the plural form - except at the beginning of sentences and in markdown titles
- Use plural (i.e.
- If the TF product or multiple product instances could be meant: Prefer the TF product variant if possible. Only use the plural variant where clearly more suitable.
- This rule - including the capitalization aspects - only applies to TF products (resp. Toolkit components). Using the
plural form with title case for other entities like i.e. Notarization Methods (
Locked Notarization,Dynamic Notarization- see below) is OK. - If onchain objects of the TF product are addressed:
- In source code documentation related to the TF product specific Move object type (i.e.
AuditTrail,Notarization), the type name followed by "object" resp. "objects" shall be used (examples: "To create aNotarizationobject use ...", "AuditTrailobjects can be batch deleted using ..."). - In less technical documentation, typically in the context of general descriptions of TF products or Notarization Toolkit components:
- the product name in singular of plural form shall be used (see above) without any extensions
- if the onchain object, equivalent to the product itself, is addressed, use either the Move object type based form (see above)
or the product name followed by "object" resp. "objects" (examples: "
Notarizationonchain objects facilitate ...", "Audit Trails on-chain objects must be managed ... ") whatever is most suitable.
- In source code documentation related to the TF product specific Move object type (i.e.
- If the product is meant itself:
- Regarding Single Notarization (Component/TF product):
- Single Notarization provides two Notarization Methods: Locked Notarization and Dynamic Notarization
- There might be additional Notarization Methods in future versions of Single Notarization (i.e. "Custom Notarization")
- For Notarization Methods, the following can be used to describe or identify the method (whatever suites into the context the best):
- Short Name:
Locked,Dynamic, ... - Full Name:
Locked Notarization,Dynamic Notarization, ...
- Short Name:
- Single Notarization provides two Notarization Methods: Locked Notarization and Dynamic Notarization
- Each TF product/component provides packages for Move, Rust and WASM/TypeScript:
- Do not use terms like
toolkit,SDK, ... for the packages - only use the termPackage - Use standard capitalization for the word
Package(includes "title case" where needed) - use "Package" or "package" whatever suites into the context the best. In this stylguidePackageis used for referencing the term. - Aspects regarding the use of
Packagefor software development in general:- For Move the term
Packageis allways used - In Rust contexts, the term
Packagedenotes a bundle of one or more crates containing a Cargo. toml file. Use the termCrateandPackagewhatever suites into the context the best - For WASM:
- The term
Packagecan have two meanings:- The WASM-Rust package containing the WASM binding code
- If the documentation refers to this aspect (i.e. explaining the existence of wasm bindings in the Rust package)
the term "wasm bindings" instead of
Packageis OK.
- If the documentation refers to this aspect (i.e. explaining the existence of wasm bindings in the Rust package)
the term "wasm bindings" instead of
- The JS/TS package created out of the WASM-Rust binding code using wasm-bindgen
- The WASM-Rust package containing the WASM binding code
- In most contexts this doesn't need to be distinguished, so just use the term
Package
- The term
- For Move the term
- Do not use terms like
cargo build --workspace --tests --examples
cargo check -p notarization-rs
cargo check -p audit-trail-rs# Tests must run single-threaded (IOTA sandbox requirement)
cargo test --workspace --release -- --test-threads=1
# Single test
cargo test --release -p notarization-rs test_name -- --test-threads=1
# Move contract tests (from notarization-move/ or audit-trail-move/)
iota move testcargo clippy --all-targets --all-features
cargo fmt --all
cargo fmt --all -- --check # check onlynpm install
npm run build
npm test # Node.js tests
npm run test:browser # Cypress browser tests# From notarization-move/ or audit-trail-move/
./scripts/publish_package.sh
./scripts/notarize.shExamples require the relevant Move package to be published first.
Notarization examples — from the repo root:
# Publish the package and capture the package ID
export IOTA_NOTARIZATION_PKG_ID=$(./notarization-move/scripts/publish_package.sh)
# Run a specific example
cargo run --release --example <example_name_goes_here>To run all notarization examples:
# Make sure IOTA_NOTARIZATION_PKG_ID is set as shown above
./examples/run.shAudit Trail examples — from the repo root:
# Publish the package; on localnet both vars are set to the same package ID
eval $(./audit-trail-move/scripts/publish_package.sh)
# Run a specific example
cargo run --release --example <example_name_goes_here>The eval form is required because the publish script prints shell export statements for two variables:
IOTA_AUDIT_TRAIL_PKG_ID— the Audit Trail package IDIOTA_TF_COMPONENTS_PKG_ID— the TfComponents package ID (equalsIOTA_AUDIT_TRAIL_PKG_IDon localnet)
- Create the source file under
examples/notarization/orexamples/audit-trail/. - Add an
[[example]]entry toexamples/Cargo.tomlpointing to the new file. - Use
examples::get_funded_notarization_client()(notarization) orexamples::get_funded_audit_trail_client()(Audit Trail) fromexamples/utils/utils.rsto obtain a funded, signed client. Do not inline client construction in example files.
Reference implementation: examples/audit-trail/01_create_audit_trail.rs
Client setup — get_funded_audit_trail_client() reads IOTA_AUDIT_TRAIL_PKG_ID and IOTA_TF_COMPONENTS_PKG_ID from the environment and returns AuditTrailClient<InMemSigner>.
Creating a trail — use the builder returned by client.create_trail():
let created = client
.create_trail()
.with_trail_metadata(ImmutableMetadata::new("name".into(), Some("description".into())))
.with_updatable_metadata("mutable status string")
.with_initial_record(InitialRecord::new(Data::text("content"), Some("metadata".into()), None))
.finish()
.build_and_execute(&client)
.await?
.output; // TrailCreated { trail_id, creator, timestamp }The creator automatically receives an Admin capability object in their wallet.
Defining a role — use the trail handle's access API with the implicit Admin capability:
client
.trail(trail_id)
.access()
.for_role("RecordAdmin")
.create(PermissionSet::record_admin_permissions(), None)
.build_and_execute(&client)
.await?;PermissionSet convenience constructors: admin_permissions(), record_admin_permissions(), role_admin_permissions(), locking_admin_permissions(), tag_admin_permissions(), cap_admin_permissions(), metadata_admin_permissions().
Issuing a capability — mint a capability object for a role:
let cap = client
.trail(trail_id)
.access()
.for_role("RecordAdmin")
.issue_capability(CapabilityIssueOptions::default())
.build_and_execute(&client)
.await?
.output; // CapabilityIssued { capability_id, target_key, role, issued_to, valid_from, valid_until }Use CapabilityIssueOptions { issued_to, valid_from_ms, valid_until_ms } to restrict who may use the capability or set a validity window.
Key types (from audit_trails::core::types): Data, InitialRecord, ImmutableMetadata, LockingConfig, LockingWindow, TimeLock, Permission, PermissionSet, CapabilityIssueOptions, RoleTags.
Reference implementations: examples/notarization/01_create_locked_notarization.rs and examples/notarization/02_create_dynamic_notarization.rs.
Use examples::get_funded_notarization_client() to get a NotarizationClient<InMemSigner>. Read audit-trail-rs/tests/e2e/ for detailed usage of every API surface.
The root Cargo.toml defines a workspace with members: notarization-rs, audit-trail-rs, examples. The WASM crates (bindings/wasm/*) are excluded from the workspace and built separately.
notarization-rs/— Rust client library for notarizationnotarization-move/— Move smart contracts for notarizationaudit-trail-rs/— Rust client library for Audit Trailaudit-trail-move/— Move smart contracts for Audit Trailbindings/wasm/notarization_wasm/— JS/TS WASM bindings for notarizationbindings/wasm/audit_trail_wasm/— JS/TS WASM bindings for Audit Trailexamples/— Rust examples (basic CRUD + real-world scenarios like IoT, legal contracts)
When performing checks and edits always ignore the folders and files defined in the .gitignore file.
Both notarization-rs and audit-trail-rs follow the same pattern:
- Full client (
NotarizationClient/AuditTrailClient): Signs and submits transactions - Read-only client (
NotarizationClientReadOnly/AuditTrailClientReadOnly): Read-only state inspection - Clients wrap a
product_commontransaction builder that supports.build(),.build_and_execute(), and.execute_with_gas_station()
Notarization creation uses a NotarizationBuilder<T> with phantom type states to enforce valid configurations at compile time. Separate builder paths exist for Dynamic (mutable, transferable) vs Locked (immutable, non-transferable) notarizations.
- Dynamic: State and metadata are updatable after creation; supports transfer locks
- Locked: State and metadata are immutable; supports time-based destruction
- Transfer locks:
None,UnlockAt(epoch),UntilDestroyed - Delete locks: Restrict when a notarization can be destroyed
Code uses #[cfg(target_arch = "wasm32")] guards to conditionally compile for WASM. Features send-sync, gas-station, default-http-client, and irl control optional capabilities.
The permission-set convenience constructors (admin_permissions(), record_admin_permissions(),
role_admin_permissions(), …) exist in all three layers and each layer's doc comment enumerates
the permissions the set contains. The Move and Rust implementations are independent and must
return the same set; the WASM implementation delegates to Rust, but its doc list does not follow
automatically. When adding a Permission variant or changing any *_permissions() set:
- Update the Move constructor implementation and its doc bullet list (
permission.move). - Mirror the change in the Rust
PermissionSetconstructor implementation and its doc list. - Update the enumerated doc list of the matching
WasmPermissionSetconstructor.
Doc lists must always be verified against the implementation of their own layer, not copied doc-to-doc — doc lists can agree across layers and still all be stale.
Every public Move event struct (public struct <Event> has copy, drop) must have counterparts in
the other two layers of the same product:
- A Rust deserialization struct in
<product>-rs/src/core/types/event.rswith matching field names, following the existing patterns there (e.g.deserialize_number_from_stringfor string-encodedu64timestamps in Audit Trails). - A WASM payload type (
Wasm<Event>with#[wasm_bindgen]) plus aFrom<<Event>>impl in the bindings'types.rs. - A section in the product's
api_mapping.tomlmapping the Move event to both counterparts (use theupdate-api-mappingskill).
Every function that emits the event must document the emission in all three layers: the
Emits a `<Event>` event on success. paragraph in Move (see MOVE-DOC-STYLEGUIDE.md), prose in
the Rust transaction-type doc ("On success a <Event> event is emitted."), and the
Emits a {@link <Event>} event on success. line in WASM (see bindings/wasm/DOC-STYLEGUIDE.md)
on both the transaction wrapper type and the handle method.
iota-sdk(v1.19.1, from IOTA git) — on-chain interactioniota_interaction/iota_interaction_rust/iota_interaction_ts— fromproduct-corerepo,feat/tf-compoenents-devbranchproduct_common— transaction builder abstraction fromproduct-coresecret-storage(v0.3.0) — key management
- Tests require an IOTA sandbox running locally
- Always use
--test-threads=1(tests share sandbox state) - Notarization examples require
IOTA_NOTARIZATION_PKG_IDenvironment variable set to the deployed package ID - Audit trail examples require
IOTA_AUDIT_TRAIL_PKG_ID(andIOTA_TF_COMPONENTS_PKG_IDon localnet) — useeval $(./audit-trail-move/scripts/publish_package.sh)to set both - WASM browser tests use Cypress
Minimum: 1.85, Edition: 2024