---
# System prepended metadata

title: IOTA Trails Tip

---

# IOTA Trails TIP

---
tip: ?
title: IOTA Trails
description: IOTA Trails specification
author: José M. Cantera (@jmcanterafonseca-iota) <jose.cantera@iota.org>
status: Draft
type: Informational
created: 2024-03-15
requires: TIP-18 (Multi-Asset Ledger and ISC Support)
---

## Abstract

Proposal to allow builders to immutable store data (or commitments to data) on the IOTA Ledger through Trails. 

A Trail is a data structure composed of a chain of immutable records that contain data (or commitments to data) relevant for an application. 

## Motivation

### Introduction

Trails can be used to immutably store data subject to different revisions, while chain constraints of the Ledger ensure auditability. This feature is particularly useful for Audit Trails (but not limited to). See figure below on the relationship between a Trail and an Output on the IOTA Ledger subject to a chain contraint. 

![trail](https://hackmd.io/_uploads/B12roUQa6.png)

This specification proposes to use Alias IDs, Stardust [TIP 18](https://github.com/iotaledger/tips/blob/tip54/tips/TIP-0018/tip-0018.md), to incarnate Trails on the IOTA  Ledger, thus a Trail's last record is always found on the Ledger (within the last Alias Output of the Trail's Alias ID), without the need for a full archive node (Permanode). As it happens with DIDs, Trails need storage deposits through tokens provided by a *Funding Address*. 

Only the *Trail Controller's Address* (the State Controller address of its Alias ID) can add new records to a Trail. The addition of a new Record to a Trail is performed through a transaction that consumes the last Alias Output and books the next Alias Output (which will contain the newly added record). Once such a transaction is confirmed the Trail's last record will be registered immutably on the Ledger.

### Object Traceability and Audit Trails

A typical application of the IOTA Trail abstraction is the Audit Trail of an object (for instance an item in the supply chain) that can be made auditable by registering its complete history (or the history of one of its properties, for instance, the "location") across time. Every time the object state changes, its properties can be updated in a regular off-chain datastore (for instance a database). As the item is auditable, the old state is kept (history remains on the database) while committing the new state to its Audit Trail (i.e. adding a new record to an IOTA Trail). In this case, the referred item is not only auditable through the history stored on the off-chain datastore but also verifiable through an IOTA Trail, guaranteeing the audit historical data cannot be tampered with.

This concept is illustrated on a more formal basis below. 

```text
Auditable-Item = Sequence (Item State 1, Item State 2, ... ,Item State n)
Audit-Trail = Sequence [Record 1, Record 2, ... ,Record n]
Record i = Data Commitment(Item State i)
```

## Specification

### Date Representation

Along this specification JSON field values that represent dates must be a calendar date together with a time of day, expressed in UTC, using the *ISO 8601 Complete Representation* and in particular using the 'Extended Format', as described below:
- The timestamp shall be a string containing *Year, Month, Day, Hours, Minutes, Seconds and time zone* components using the format `YYYY-MM-DDThh:mm:ssZ` as defined in [ISO 8601](http://www.iso.org/iso/catalogue_detail?csnumber=40874). In this representation, the character `-` is used to separate the calendar date components, the character `T` is used to indicate the start of the time of day portion, the character `:` is used to separate the time of day components, and the trailing character `Z` is used to convey the time zone.
- All the referred components shall appear in the string; reduced representations are not permitted.
- The trailing timestamp component shall contain the time zone related information and shall always be equal to the character `Z`. Therefore, all timestamps shall be expressed in UTC.

### Trail Identification

An IOTA Trail is identified by a [URN](https://tools.ietf.org/html/rfc8141) as follows:

* Namespace identifier: NID = `"trail"`
* Namespace specific string: NSS = `"iota"` `":"` `<HRP_Network>` `":"` `<Alias_ID>`

where:

* *HRP_Network*. The Bech32 HRP corresponding to the subject network on which the Trail is registered, ex. `main`
* *Alias_ID*. The Alias ID of the Alias entry on the Ledger that incarnates the Trail (represented as 64 hexadecimal chars).

Example:
    
```
urn:trail:iota:main:0x032551db651a69d8191356628e4d9ed50e3a89aa8d425f01b16303e213b062f3
```   
    
### Trail Data Model

A Trail is composed by the following data elements (if nothing is said, by default an element is mandatory):

* *Trail ID*, represented by a URN as defined by this [specification](#Trail-Identification).
* *State Index*. An index that can be used to derive how many states a Trail has gone through. Actually the number of records a Trail has been subject to corresponds to its *State Index* plus `1`, i.e the first state index of a trail must be `0`.
* *Immutable data*. Data that have been defined at Trail creation time and which shall remain immutable during the Trails' lifetime. It is **optional**. Immutable data must be represented as a non-empty [JSON](https://tools.ietf.org/html/rfc8259) Object.
* *Trail Record(s)* i.e. Trail data. Even though this can be a collection of records, only the last record is guaranteed to be found on a Ledger entry of every node. A Trail Record must be represented as a non-empty JSON Object.
* *Trail metadata*:
    - *type* (immutable). Trail's type(s) represented as a String or a String Array.
    - *creation date* (immutable). The [ISO8601 date](#Date-Representation) that represents the creation date of the Trail (i.e. the timestamp at which the transaction to create the Trail was issued).
    - *update date*. The ISO8601 date that represents when the Trail was updated (i.e. the timestamp of the transaction issued to add the last record of a Trail).
    - *Trails's Controller*. The IOTA Address that controls the Trail's state and which can add new records to the Trail.
    - *Trails' Governor*. The IOTA Address that controls the Trail and which can change the Trail's Controller or even delete the Trail. Usually the Trail's Controller and Governor coincide.
    - *Trail's Issuer* (immutable). It must correspond to the IOTA Address that provided the funds to create the Trail.
    - *inclusion milestone(s)* i.e. milestones that have confirmed the inclusion of the Trail on the Ledger. The most important ones are the first inclusion milestone (when the Trail was initially created) and the last inclusion milestone (when the Trail was updated for the last time). It is **optional** for implementations to inform about them.

### Trail Representation on the Stardust Ledger

A Trail is represented by an *Alias ID* and the Trail Data Structure is mapped to an *Alias Output* as follows:

#### Immutable Issuer

It must correspond to the *Funding Address* of the Trail i.e the address that provided the initial funds to create the Trail.
    
#### Immutable Metadata
    
It must store the serialization of a JSON Object that must contain at least the following members:
    
* `meta`. The value associated with this name must be a JSON Object with the following sub-members:
    + `type`: Its value must be one of:
        +  the JSON String `"TRAIL:1.0"` (`\x54\x52\x41\x49\x4c\x3a\x31\x2e\x30`) 
        +  a JSON Array of Strings of which at least one of the strings of the array must contain the aforementioned `"TRAIL:1.0"` String.
    + `created`: Its value must correspond to the Trail's creation date (as defined [above](#Trail-Data-Model)).
* `record`. The value associated with this name must be a JSON Object which members have to be defined at Trail creation time (application-dependent) and which will remain immutable along the lifetime of the Trail. In case there are no immutable fields associated to the Trail, this member *shall not* appear in the serialized JSON. 

Example 1:

```json
{
    "meta": {
        "type": "TRAIL:1.0",
        "created": "2024-03-04T10:46:31Z"
    },
    "record": {
        "itemId": "urn:epc:id:sgtin:9524444.100000.demo-3"
    }
}
```

Example 2:

```json
{
    "meta": {
        "type": ["TRAIL:1.0", "RESOURCE:1.0"],
        "created": "2024-03-04T10:46:31Z"
    },
    "record": {
        "historyLocator": "ipns://k51qzi5uqu5dgxz1uzygtt6mz5m6xxujjvlheiz8259doz0x0s5my4onob7a3s"
    }
}
```

#### State Metadata

It must store the JSON serialization of an Object that must contain at least the following members:

* `meta` The value associated with this name must be a JSON Object with at least the following sub-members:
    + `updated`: Its value must correspond to the Trail's update date (as defined [above](#Trail-Data-Model)).
* `record` The value associated with this member must be a **non-empty JSON object** and must contain the last record of the Trail. The content of this field is application-dependent. For the avoidance of doubt, empty Trail Records are not allowed. 

Example:

```json
{
  "meta": {
    "updated": "2023-11-10T14:33:23Z"
  },
  "record": {
    "fingerprintId": "ni:///sha-256;558aa9a386e4c2d5b7bb1a95673d001948d77ac8414674d0d7d8401e13b8b20e",
    "versionNumber": 3
  }
}
```

#### State Controller Address

The Trail's Controller address must be mapped to the Alias State Controller Address. 

#### Governor Address

The Trail's Governor address must be mapped to the Alias Governor Address.

### Trail implementation behaviour

Compliant software must behave as follows: 

#### Trail Creation

Preconditions: 

* An IOTA Node connected to an IOTA Stardust network available, ex. mainnet. 
* A Trail Controller Address is known, represented using the Bech32 format. Trails can be controlled by Alias IDs, thus a Trail can be controlled by a DID, or even by another Trail.
* A Funding Address for the Trail is known and controlled. This is the Adress that will provide the necessary funds for the storage deposit associated with the Alias Output that will represent the Trail on the Ledger.
* A Trail's Governor Address , represented using the Bech32 format (**Optional**). If it is not defined then the Trail's Governor Address must be equal to the Trails' Controller Address.

Note: The Trail Controller Address and the Funding Address might be the same.

Trail's Data:

* Trail initial record represented as non-empty JSON Object <-- `Record`. 
* Optionally, immutable data represented as non-empty JSON Object. <-- `Immutable Record` 
* Optionally, Trail's type, represented as a single JSON String or a JSON Array of Strings <-- `Trail_Type`


Behaviour:

1. Prepare an Alias Output in accordance with this specification, i.e. : 
    - `ImmutableMetadata` <- [Specification](#Immutable-Metadata): 
        - The `meta.created` timestamp must be filled out with the current date and time (and represented as an [ISO8601 string](#Date-Representation)). 
        - If `Trail_Type` is defined, the `meta.type` member's value must be a JSON String Array containing the `"TRAIL:1.0"` String and the extra type(s) (represented as JSON Strings) conveyed by the `Trail_Type`.
        - If `Trail_Type` is not defined, the `meta.type` member's value must be one of: 
            - JSON String `"TRAIL:1.0"` 
            - JSON Array `["TRAIL:1.0"]`.
    - `StateMetadata` <- [Specification](#State-Metadata). The `meta.updated` timestamp must be equal to the `meta.created` timstamp conveyed at the Immutable Metadata field. 
    - `StateIndex` <- `0`
    - `ImmutableIssuer` <- Funding Address.
    - `State Controller Address` <- Trails's Controller Address.
    - `Governor Address` <- Trail's Governor Address. 
2. Calculate the storage deposit needed
3. Find one or more suitable Basic Outputs hold by the Funding Address that can fund the storage deposit needed. A filtering criteria for funding Outputs may be specified by the Trails' issuer.
    1. If there is no an Output or set of Outputs holded by the Funding Address that can fund the transaction an error must be raised and this process must finish.
4. Prepare a transaction that involves as Input one or more Basic Outputs owned by the Funding Addresss (as much as needed to fund the storage deposit formerly calculated) and as Output an Alias Output equal to the one prepared at step #1.
5. Issue the transaction, calculate the Alias ID, wait for transaction confirmation. 
6. Generate the URN of the new minted Trail, as per the [Specification](#Trail-Identification).
7. Return the Trail URN and the metadata (*creation date, update date, Trail Controller Address, State Index*). Implementations may also return the storage cost and the Block ID of the minting transaction (that can yield to the confirmation milestone).


#### Trail Resolution

Preconditions: 

* An IOTA Node connected to an IOTA Stardust network available, ex. mainnet. 
* An existing IOTA Trail identified by a URN.

Behaviour:

1. Parse the URN that represents the TrailID to obtain the Alias ID --> `AliasID`. 
2. Determine the last Alias Output ID associated with the Alias ID --> `AliasOutputID`.
    1. If the AliasID cannot be found on the Ledger then raise an error and finish this process. 
3. Read from the Node the `AliasOutputID` and set it to `AliasOutput`. 
    1. If the `AliasOutputID` cannot be found on the Ledger then raise an error and finish this process.
4. Check `AliasOutput.ImmutableMetadata` as follows and raise an error and finish under the following conditions:
    1. Parse the bytes content as JSON and there is a parse error.
    2. If the parsed JSON content does not correspond to a JSON Object.
    3. If the parsed JSON Object does not include a key `meta` with a subkey `type` equal to the string `"TRAIL:1.0"`, and if the referred subkey `type` does not correspond to a JSON Array of Strings that contains at least the string `"TRAIL:1.0"`.
    4. If the parsed JSON Object does not include a key `meta` with a subkey `created` equal to a valid timestamp as per this [specification](#Date-Representation).
    5. If `meta.created`denotes a timestamp in the future.
    6. If `meta.created` is after the `AliasOutput.metadata.milestoneTimestampBooked`.
    7. If the parsed JSON Object includes a `record` member with a value that is not a JSON Object or such a JSON Object is empty.
5. Check `AliasOutput.StateMetadata` as follows and raise an error and finish under the following conditions:
    1. Parse the bytes content as JSON and there is a parse error.
    2. If the parsed JSON content does not correspond to a JSON Object.
    3. If the parsed JSON Object does not include a key `meta` with a subkey `updated` equal to a valid timestamp as per this [specification](#Date-Representation).
    4. If `meta.updated` timestamp is before the `meta.created` timestamp found at the `AliasOutput.ImmutableMetadata` field.
    5. If the parsed JSON Object does not include a `record` member.
    6. If the parsed JSON Object includes a `record` member with a value that is not a JSON Object or such a JSON Object is empty.
6. At this point in time the Alias Output content is considered to be a valid Trail. Let the result of this process be a JSON Object named `Trail`. Such a JSON object must be filled in as follows (and returned to the caller as a result of the resolution process):
    1. `Trail.trail.id` <- Trail ID URN
    2. `Trail.trail.stateIndex` <- `AliasOutput.StateIndex`
    3. `Trail.trail.record` <- `JSON.parse(AliasOutput.StateMetadata).record`
    4. `Trail.trail.immutable` <- `JSON.parse(AliasOutput.ImmutableMetadata).record`
    5. `Trail.meta.created` <- `JSON.parse(AliasOutput.ImmutableMetadata).meta.created`
    6. `Trail.meta.updated` <- `JSON.parse(AliasOutput.StateMetadata).meta.updated`
    7. `Trail.meta.stateControllerAddress` <- `AliasOutput.StateControllerAddress`
    8. `Trail.meta.type` <- `JSON.parse(AliasOutput.ImmutableMetadata).meta.type`

Implementations may add additional metadata such as the inclusion milestones. 


#### Trail Record Addition

Preconditions:

* An IOTA Node connected to an IOTA Stardust network available, ex. mainnet. 
* An existing IOTA Trail identified by a URN.
* Trail Controller Address's private key is known.
* Optionally a Funding Address (if the new record added needs additional storage deposit) that hold enough funds to fund the operation.

Note: The Trail Controller Address and the Funding Address might be the same.

Trails's Data: 

* New record available represented as non-empty JSON Object.

Behaviour:

1. Parse the URN that represents the TrailID to obtain the Alias ID --> `AliasID`. 
2. Determine the last Alias Output ID associated with the Alias ID --> `AliasOutputID`.
    2.1 If the `AliasID` cannot be found on the Ledger then raise an error and finish this process. 
    2.2 If the AliasID is valid and found on the Ledger but does not correspond to an IOTA Trail, as mandated by [Trail-Resolution](#Trail-Resolution) steps 1 to 5, then raise an error and finish this process
3. Read `AliasOutputID` from the Ledger into `PreviousAliasOutput`, clone it and use such a clone to prepare a new Alias Output in accordance with this specification, i.e. :
    - `StateMetadata` <- [Specification](#State-Metadata). The `meta.updated` timestamp must be filled out with the current date and time (and represented as an [ISO8601 string](#date-representation)).
    - `StateIndex` <- `PreviousAliasOutput.StateIndex + 1`.
    - AliasID <- `PreviousAliasOutput.AliasID`
    - Rest of the fields of the new Alias Output must remain untouched from the clone.
4. Calculate the storage deposit needed, in order to determine whether additional funds are needed or not. 
    1. If no additional funds are needed, then prepare a transaction that unlocks `AliasOutputID` (with the Trail Controller Address' private key) and creates a new Alias Output (under the same `AliasID`). 
    2. If additional funds are needed, then prepare a transaction that unlocks as Inputs one or more Basic Outputs owned by the Funding Address and the `AliasOutputID` (unlocked with the Trail Controller Address' private key) and as Output a new Alias Output as the one prepared at step #3. 
5. Issue the transaction to the network, wait for confirmation. 
6. Return the Trail URN and the metadata (*creation date, update date, Trail controller address, State Index*). Implementations may also return the storage cost and the Block ID of the transaction (that can yield to the confirmation milestone).


## Nova considerations

The implementation of IOTA Trails in Nova is feasible as an IOTA Trail corresponds to an Anchor Output, [TIP 54](https://github.com/iotaledger/tips/blob/tip54/tips/TIP-0054/tip-0054.md). The deviation from the Stardust specification is in the new structure of the State Metadata and Immutable Metadata fields which are structured in Nova as key-value pairs. The proposal for Nova is:

* State Metadata. It must contain two keys, named `meta` and `record` which content must be the JSON serialization of the respective JSON objects as specified at [State-Metadata](#State-Metadata). 
* Immutable Metadata. It must contain two keys, named `meta` and `record` which content must the JSON serialization of the respective JSON objects as specified at [Immutable-Metadata](#Immutable-Metadata). `record` key is optional as per this specification. 

----
END OF TIP
____

