Clock Module
The iota::clock module provides APIs for accessing time from Move contracts through a unique shared object at address 0x6.
Source: crates/iota-framework/packages/iota-framework/sources/clock.move
Core Type
Clock
Singleton shared object that exposes time to Move calls:
public struct Clock has key {
id: UID,
timestamp_ms: u64,
}
Key properties:
- Found at address
0x6
- Can only be read (immutable reference) by entry functions
- Automatically updated by system transactions
- Timestamp is milliseconds since an arbitrary point in the past
Accessing Time
timestamp_ms
Get the current timestamp in milliseconds:
Immutable reference to the Clock object
public fun timestamp_ms(clock: &Clock): u64
Example:
use iota::clock::{Self, Clock};
public fun get_current_time(clock: &Clock): u64 {
clock.timestamp_ms()
}
Usage Patterns
Entry Function with Clock
Entry functions can accept the Clock as a parameter:
use iota::clock::{Self, Clock};
public entry fun check_deadline(clock: &Clock, deadline: u64) {
let current_time = clock.timestamp_ms();
assert!(current_time <= deadline, 0);
}
Time-Based Logic
Implement time-dependent functionality:
module example::auction {
use iota::clock::{Self, Clock};
use iota::object::{Self, UID};
use iota::transfer;
use iota::tx_context::TxContext;
public struct Auction has key {
id: UID,
end_time: u64,
highest_bid: u64,
}
public fun create_auction(
duration_ms: u64,
clock: &Clock,
ctx: &mut TxContext
) {
let auction = Auction {
id: object::new(ctx),
end_time: clock.timestamp_ms() + duration_ms,
highest_bid: 0,
};
transfer::share_object(auction);
}
public fun place_bid(
auction: &mut Auction,
bid: u64,
clock: &Clock
) {
assert!(clock.timestamp_ms() < auction.end_time, 0);
assert!(bid > auction.highest_bid, 1);
auction.highest_bid = bid;
}
public fun is_auction_ended(auction: &Auction, clock: &Clock): bool {
clock.timestamp_ms() >= auction.end_time
}
}
Cooldown Mechanism
Implement cooldown periods:
module example::game {
use iota::clock::{Self, Clock};
use iota::object::{Self, UID};
public struct Character has key {
id: UID,
last_action_time: u64,
cooldown_ms: u64,
}
const ECooldownNotFinished: u64 = 0;
public fun perform_action(
character: &mut Character,
clock: &Clock
) {
let current_time = clock.timestamp_ms();
assert!(
current_time >= character.last_action_time + character.cooldown_ms,
ECooldownNotFinished
);
character.last_action_time = current_time;
// Perform the action
}
public fun cooldown_remaining(
character: &Character,
clock: &Clock
): u64 {
let current_time = clock.timestamp_ms();
let ready_time = character.last_action_time + character.cooldown_ms;
if (current_time >= ready_time) {
0
} else {
ready_time - current_time
}
}
}
Vesting Schedule
Implement token vesting:
module example::vesting {
use iota::clock::{Self, Clock};
use iota::object::{Self, UID};
use iota::balance::{Self, Balance};
use iota::coin::{Self, Coin};
public struct VestingContract<phantom T> has key {
id: UID,
total_amount: u64,
start_time: u64,
duration_ms: u64,
claimed: u64,
balance: Balance<T>,
}
public fun create_vesting<T>(
balance: Balance<T>,
start_time: u64,
duration_ms: u64,
ctx: &mut TxContext
) {
let total = balance.value();
let vesting = VestingContract {
id: object::new(ctx),
total_amount: total,
start_time,
duration_ms,
claimed: 0,
balance,
};
transfer::transfer(vesting, ctx.sender());
}
public fun claim_vested<T>(
vesting: &mut VestingContract<T>,
clock: &Clock,
ctx: &mut TxContext
): Coin<T> {
let current_time = clock.timestamp_ms();
let vested = calculate_vested_amount(vesting, current_time);
let claimable = vested - vesting.claimed;
vesting.claimed = vested;
let balance = vesting.balance.split(claimable);
balance.into_coin(ctx)
}
fun calculate_vested_amount<T>(
vesting: &VestingContract<T>,
current_time: u64
): u64 {
if (current_time < vesting.start_time) {
return 0
};
let end_time = vesting.start_time + vesting.duration_ms;
if (current_time >= end_time) {
return vesting.total_amount
};
let elapsed = current_time - vesting.start_time;
(vesting.total_amount * elapsed) / vesting.duration_ms
}
}
Important Restrictions
Immutable Reference Only
Entry functions cannot accept Clock by mutable reference or value:
// ✓ Correct - immutable reference
public entry fun valid_function(clock: &Clock) { }
// ✗ Wrong - mutable reference (will fail verification)
public entry fun invalid_function(clock: &mut Clock) { }
// ✗ Wrong - by value (will fail verification)
public entry fun invalid_function(clock: Clock) { }
Validators will reject transactions that attempt to use Clock incorrectly.
System-Only Updates
The Clock timestamp is updated automatically:
#[allow(unused_function)]
fun consensus_commit_prologue(clock: &mut Clock, timestamp_ms: u64, ctx: &TxContext) {
assert!(ctx.sender() == @0x0, ENotSystemAddress);
clock.timestamp_ms = timestamp_ms
}
Only system transactions (sender @0x0) can modify the Clock.
Clock Address
The Clock is always located at address 0x6:
const IOTA_CLOCK_OBJECT_ID: address = @0x6;
Testing Support
The module provides testing utilities:
#[test_only]
public fun create_for_testing(ctx: &mut TxContext): Clock
#[test_only]
public fun share_for_testing(clock: Clock)
#[test_only]
public fun increment_for_testing(clock: &mut Clock, tick: u64)
#[test_only]
public fun set_for_testing(clock: &mut Clock, timestamp_ms: u64)
#[test_only]
public fun destroy_for_testing(clock: Clock)
Example test:
#[test]
fun test_auction() {
let ctx = &mut tx_context::dummy();
let mut clock = clock::create_for_testing(ctx);
clock.set_for_testing(1000);
// Test auction logic
clock.destroy_for_testing();
}
Error Codes
const ENotSystemAddress: u64 = 0; // Sender is not @0x0
Complete Example: Time-Locked Safe
module example::time_lock {
use iota::clock::{Self, Clock};
use iota::object::{Self, UID};
use iota::transfer;
use iota::tx_context::TxContext;
const ENotUnlocked: u64 = 0;
public struct TimeLockSafe<T: key + store> has key {
id: UID,
unlock_time: u64,
contents: Option<T>,
}
public fun create<T: key + store>(
item: T,
unlock_time: u64,
ctx: &mut TxContext
) {
let safe = TimeLockSafe {
id: object::new(ctx),
unlock_time,
contents: option::some(item),
};
transfer::transfer(safe, ctx.sender());
}
public fun unlock<T: key + store>(
safe: TimeLockSafe<T>,
clock: &Clock,
ctx: &TxContext
): T {
assert!(
clock.timestamp_ms() >= safe.unlock_time,
ENotUnlocked
);
let TimeLockSafe { id, unlock_time: _, contents } = safe;
id.delete();
contents.destroy_some()
}
public fun is_unlocked<T: key + store>(
safe: &TimeLockSafe<T>,
clock: &Clock
): bool {
clock.timestamp_ms() >= safe.unlock_time
}
public fun time_remaining<T: key + store>(
safe: &TimeLockSafe<T>,
clock: &Clock
): u64 {
let current = clock.timestamp_ms();
if (current >= safe.unlock_time) {
0
} else {
safe.unlock_time - current
}
}
}
Best Practices
-
Always use immutable reference: Never try to mutate the Clock in user code
-
Check time constraints early: Verify time-based conditions at the start of functions
-
Use milliseconds: All timestamps are in milliseconds since epoch
-
Test with mock clock: Use testing utilities to simulate different times
-
Consider time zones: The timestamp is UTC-based and doesn’t include timezone information
-
Handle edge cases: Check for potential overflow when adding durations to timestamps