Skip to main content

Accessing On-Chain Time in IOTA

When you need to access network-based time for your transactions on IOTA, you have several options:

  • Near Real-Time Measurement: Use the immutable reference of time provided by the Clock module in Move. This value updates with every network checkpoint.
  • Epoch Start Time: Use the epoch_timestamp_ms function to capture the precise moment the current epoch started.

Using the iota::clock::Clock Module

To access a prompt timestamp, you can pass a read-only reference of iota::clock::Clock as an entry function parameter in your transactions. An instance of Clock is provided at the address 0x6, and no new instances can be created.

Use the timestamp_ms function from the iota::clock module to extract a Unix timestamp in milliseconds.

    /// The `clock`'s current timestamp as a running total of
/// milliseconds since an arbitrary point in the past.
public fun timestamp_ms(clock: &Clock): u64 {
clock.timestamp_ms
}

Example: Emitting an Event with a Timestamp

The following example demonstrates an entry function that emits an event containing a timestamp from the Clock:

module basics::clock {
use iota::{clock::Clock, event};

public struct TimeEvent has copy, drop, store {
timestamp_ms: u64,
}

entry fun access(clock: &Clock) {
event::emit(TimeEvent { timestamp_ms: clock.timestamp_ms() });
}
}

To call the previous entry function, pass 0x6 as the address for the Clock parameter:

iota client call --package <EXAMPLE> --module 'clock' --function 'access' --args '0x6'

Note: The Clock timestamp changes at the rate the network generates checkpoints, which is every 1 second with Narwhal/Bullshark consensus and every 0.1 to 0.2 seconds with Mysticeti consensus.

Successive calls to iota::clock::timestamp_ms within the same transaction will always produce the same result because transactions are considered to take effect instantly. However, timestamps from Clock are monotonic across transactions that touch the same shared objects, meaning successive transactions see a greater or equal timestamp compared to their predecessors.

Transaction Requirements

  • Consensus Requirement: Any transaction that requires access to a Clock must go through consensus because the only available instance is a shared object.
  • Immutable Reference: Transactions that use the clock must accept it as an immutable reference (&Clock), not as a mutable reference or value. This prevents contention, as transactions that access the Clock can only read it.

Validators will refuse to sign transactions that do not meet this requirement, and packages that include entry functions accepting a Clock or &mut Clock will fail to publish.

Testing Clock-Dependent Code

The following functions allow you to test Clock-dependent code by manually creating a Clock object and manipulating its timestamp. This is possible only in test code:

    public fun create_for_testing(ctx: &mut TxContext): Clock {
Clock {
id: object::new(ctx),
timestamp_ms: 0,
}
}

#[test_only]
/// For transactional tests (if a Clock is used as a shared object).
public fun share_for_testing(clock: Clock) {
transfer::share_object(clock)
}

#[test_only]
public fun increment_for_testing(clock: &mut Clock, tick: u64) {
clock.timestamp_ms = clock.timestamp_ms + tick;
}

#[test_only]
public fun set_for_testing(clock: &mut Clock, timestamp_ms: u64) {
assert!(timestamp_ms >= clock.timestamp_ms);
clock.timestamp_ms = timestamp_ms;
}

#[test_only]
public fun destroy_for_testing(clock: Clock) {
let Clock { id, timestamp_ms: _ } = clock;
id.delete();
}

Here's a basic test that creates a Clock, increments it, and then checks its value:

module iota::clock_tests {
use iota::clock;

#[test]
fun creating_a_clock_and_incrementing_it() {
let mut ctx = tx_context::dummy();
let mut clock = clock::create_for_testing(&mut ctx);

clock.increment_for_testing(42);
assert!(clock.timestamp_ms() == 42);

clock.set_for_testing(50);
assert!(clock.timestamp_ms() == 50);

clock.destroy_for_testing();
}
}

Using Epoch Timestamps

If you don't need a near real-time measurement, you can use the epoch_timestamp_ms function from the iota::tx_context module to access the timestamp for the start of the current epoch. This function works for all transactions, including those that do not go through consensus:

    public fun epoch_timestamp_ms(self: &TxContext): u64 {
self.epoch_timestamp_ms
}

The function returns the point in time when the current epoch started, as a Unix timestamp in milliseconds (u64). This value changes roughly once every 24 hours when the epoch changes.

Testing Epoch-Sensitive Code

Tests based on [iota::test_scenario] can use later_epoch to exercise time-sensitive code that uses epoch_timestamp_ms:

    public fun later_epoch(
scenario: &mut Scenario,
delta_ms: u64,
sender: address,
): TransactionEffects {
scenario.ctx.increment_epoch_timestamp(delta_ms);
next_epoch(scenario, sender)
}

The later_epoch function behaves like iota::test_scenario::next_epoch (finishes the current transaction and epoch in the test scenario) but also increments the timestamp by delta_ms milliseconds to simulate the passage of time.

Question 1/2

What is the purpose of the iota::clock::Clock module?