> For the complete documentation index, see [llms.txt](https://igra-labs.gitbook.io/igralabs-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://igra-labs.gitbook.io/igralabs-docs/for-developers/igra-timestamp-mechanics.md).

# Igra Timestamp Mechanics

`block.timestamp` on Igra is **not** a real wall-clock timestamp. It's a synthetic value derived from Kaspa L1's DAA (Difficulty Adjustment Algorithm) score. Currently it runs **\~2-2.5 minutes ahead** of real time — meaning the synthetic timestamp is in the future relative to wall clock.

This means:

* A tx submitted at 9:00:00 real time gets `block.timestamp` ≈ 9:02:20
* Smart contracts using `block.timestamp` for deadlines (e.g. DEX swap expiry) see a time that hasn't happened yet from the user's perspective
* The drift is not constant — it depends on Kaspa's actual vs assumed block rate

## How It Works

### Synthetic Timestamp (what `block.timestamp` returns)

```
f(DAA) = REF_TIMESTAMP + (daaScore / 10) - (REF_DAA_SCORE / 10)
```

The formula assumes exactly 10 DAA score increments per second. When Kaspa's actual block rate differs from this assumption, the synthetic timestamp drifts from wall clock.

### Real L1 Timestamps (stored in beaconRoot)

Each Igra block covers a "window" of 1-10 L1 Kaspa blocks. The real L1 timestamps are packed into the `beaconRoot` field of each block as deltas:

```
beaconRoot (256 bits):
┌────────────┬──────────────┬──────────────────┬─────────────────────────┐
│ blockCount │ firstDaaScore│ firstTimeDelta   │ subsequentDeltas[0..8]  │
│  4 bits    │  38 bits     │  25 bits (signed)│  9 × 21 bits            │
└────────────┴──────────────┴──────────────────┴─────────────────────────┘
```

* **blockCount** — number of L1 blocks in this window (1-10)
* **firstDaaScore** — DAA score of the first L1 block
* **firstTimeDelta** — signed offset: `realTS₁ - f(DAA)`. Negative when synthetic is ahead of real (currently \~-140s)
* **subsequentDeltas** — unsigned offsets from first block: `realTSₙ - realTS₁`

### Reconstruction

To recover real L1 timestamps from beaconRoot:

```
realTS₁ = f(firstDaaScore) + signExtend(firstTimeDelta, 25)
realTSₙ = realTS₁ + subsequentDeltas[n-2]   // n is 1-based block index (2..blockCount)
```

## L2Oracle Contract

Address: [`0x7c8edd6eB8959cCcDA63D7A3e956Bb9C00BaD67c`](https://explorer.igralabs.com/address/0x7c8edd6eB8959cCcDA63D7A3e956Bb9C00BaD67c)

Key functions:

* `beaconRootAt(uint256 timestamp)` → `bytes32` — get packed beaconRoot for a block timestamp
* `decodeBeaconRoot(bytes32)` → decoded struct (blockCount, firstDaaScore, firstTimestampDelta, subsequentDeltas)
* `daaToTimestamp(uint64 daaScore)` → `uint64` — compute synthetic timestamp from DAA score

## Practical Impact

| Scenario                           | What Happens                                                                                                                                      |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| DEX swap deadline                  | Set deadline relative to `block.timestamp`, NOT `Date.now()`. The contract checks against `block.timestamp` which is \~2 min ahead of wall clock. |
| Time-locked operations             | `block.timestamp` is ahead — locks expire \~2 min earlier than expected from wall clock perspective                                               |
| Querying "when did this tx happen" | Use the oracle to get real L1 time, not `block.timestamp`                                                                                         |

### For Deadline-Based Contracts

**Wrong** (will fail if timeout < drift):

```javascript
const deadline = Math.floor(Date.now() / 1000) + 60; // wall clock + 60s
```

**Correct** (drift-proof):

```javascript
const block = await provider.getBlock('latest');
const deadline = Number(block.timestamp) + 60; // block.timestamp + 60s
```

## Reference Script

Full working script that reconstructs real L1 timestamps from any Igra block:

```javascript
const { ethers } = require("ethers");

const RPC = "https://rpc.igralabs.com:8545";
const ORACLE = "0x7c8edd6eB8959cCcDA63D7A3e956Bb9C00BaD67c";
const ABI = [
  "function decodeBeaconRoot(bytes32) view returns (tuple(uint8 blockCount, uint64 firstDaaScore, uint32 firstTimestampDelta, uint32[9] subsequentDeltas))",
  "function daaToTimestamp(uint64) view returns (uint64)",
  "function beaconRootAt(uint256) view returns (bytes32)",
];

function signExtend(value, bits) {
  const mask = 1 << (bits - 1);
  return (value ^ mask) - mask;
}

async function getL1Timestamps(blockTag = "latest") {
  const provider = new ethers.JsonRpcProvider(RPC);
  const oracle = new ethers.Contract(ORACLE, ABI, provider);

  const block = await provider.getBlock(blockTag);
  const blockTs = Number(block.timestamp);

  const beaconRoot = await oracle.beaconRootAt(blockTs);
  const decoded = await oracle.decodeBeaconRoot(beaconRoot);

  const blockCount = Number(decoded.blockCount);
  const firstDaa = BigInt(decoded.firstDaaScore);
  const syntheticTs = Number(await oracle.daaToTimestamp(firstDaa));
  const firstDelta = signExtend(Number(decoded.firstTimestampDelta), 25);

  const realTs1 = syntheticTs + firstDelta;
  const timestamps = [realTs1];
  for (let i = 0; i < blockCount - 1; i++) {
    timestamps.push(realTs1 + Number(decoded.subsequentDeltas[i]));
  }

  return {
    block: block.number,
    blockTimestamp: blockTs,
    syntheticTimestamp: syntheticTs,
    drift: firstDelta,
    l1BlockCount: blockCount,
    l1Timestamps: timestamps,
  };
}

// Usage
getL1Timestamps("latest").then(data => {
  console.log("Block:", data.block);
  console.log("block.timestamp:", new Date(data.blockTimestamp * 1000).toISOString());
  console.log("Drift:", data.drift, "seconds (negative = synthetic ahead of real)");
  console.log("L1 blocks in window:", data.l1BlockCount);
  data.l1Timestamps.forEach((ts, i) => {
    console.log("  L1 block", i + 1, "real time:", new Date(ts * 1000).toISOString());
  });
});
```

### Example Output

```
Block: 3983243
block.timestamp: 2026-04-14T17:31:50.000Z
Drift: -141 seconds (negative = synthetic ahead of real)
L1 blocks in window: 3
  L1 block 1 real time: 2026-04-14T17:29:29.000Z
  L1 block 2 real time: 2026-04-14T17:29:29.000Z
  L1 block 3 real time: 2026-04-14T17:29:30.000Z
```

Drift fluctuates based on Kaspa L1 actual block production rate vs the assumed 10 DAA/second.
