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 theClock
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.