Developer Documentation

Technical reference for the vtimestamp on-chain data format, VDXF keys, CLI verification, and MCP server integration.

On-Chain Data Structure

Each timestamp is one entry on the user's VerusID contentmultimap under the proof.basic VDXF key. The entry is a single DataDescriptor in public-encrypted mode (flags: 13): the daemon encrypts the payload with an ephemeral key and publishes the incoming viewing key (ivk) on-chain, so anyone can decrypt it without the original writer's keys.

contentmultimap: {
  "proof.basic": [
    {
      "i4GC1YGEVD21afWudGoFJVdnfjJ5XWnCQv": {
        "version": 1,
        "flags": 13,
        "objectdata": "<ciphertext, hex>",
        "epk":        "<ephemeral pubkey, hex>",
        "ivk":        "<on-chain incoming viewing key, hex>"
      }
    }
  ]
}

The outer key (proof.basic) is a VDXF key — a deterministic i-address derived from the namespace. On testnet this is testidx.vrsctest::proof.basic, on mainnet vtimestamp.vrsc::proof.basic. The wrapper key i4GC1YGEVD21afWudGoFJVdnfjJ5XWnCQv is the DataDescriptor VDXF key and is the same on both networks.

Decrypted Payload

A single decryptdata call recovers a JSON object containing every metadata field for the timestamp:

{
  "sha256":      "a7f3b2c1d4e5f6a7b8c9d0e1...",
  "title":       "Q4 Financial Report",
  "description": "Final approved version",
  "filename":    "quarterly-report.pdf",
  "filesize":    2097152
}

Payload Fields

  • sha256 — SHA-256 hash (required, 64 hex chars)
  • title — Human-readable title (required)
  • description — Optional description
  • filename — Original filename (optional)
  • filesize — File size in bytes, number (optional)

Optional fields are simply omitted from the JSON object when not present. New fields can be added without breaking existing readers.

Timestamp Derivation

The timestamp itself is not stored — it's derived from the block that contains the transaction. Use getblock with the block hash from getidentityhistory to get the block time (Unix timestamp) and height.

Why public-encrypted? The Verus convention for application data on a VerusID is to encrypt the payload at rest with an on-chain viewing key. Encryption is the storage envelope here, not a privacy mechanism — anyone running a Verus node with decryptdata available recovers the exact same JSON. Consumers all go through one uniform code path (decryptdata + JSON.parse) regardless of which app wrote the entry.

VDXF Key Reference

All keys are deterministic i-addresses derived from their namespaced names. With the single-leaf JSON payload, only two VDXF keys appear on-chain — the outer container and the DataDescriptor wrapper.

KeyTestnet I-AddressMainnet I-AddressPurpose
proof.basic (outer)i6UD4js3jqyjz9Mttmbk2Sh4eCuwLKPLyQiJvkQ3uTKmRoFiE3rtP8YJxryLBKu8enmXContainer for the timestamp entry on the identity
DataDescriptor (wrapper)i4GC1YGEVD21afWudGoFJVdnfjJ5XWnCQvDataDescriptor wrapper key (same on every chain)

Testnet keys use the testidx.vrsctest:: namespace. Mainnet keys use the vtimestamp.vrsc:: namespace. Per-field labels (.sha256, .title, …) are not minted as separate VDXF keys — field names live inside the decrypted JSON object instead, which keeps the schema extensible without registering new keys.

Verifying Programmatically

You can verify timestamps using the Verus CLI without the vtimestamp website. The primary call is getidentityhistory, which gives you per-update block info needed to prove when a timestamp was recorded. Each entry's payload is decrypted with decryptdata. A related call, getidentitycontent, is useful for a different purpose — see the alternative section below.

Step 1: Get Identity History

Retrieve every update ever made to a VerusID:

verus getidentityhistory "alice@"

Returns every updateidentity transaction for that identity. Each history entry includes height, blockhash, the full identity state at that point (including contentmultimap), and output.txid. Optional trailing arguments can narrow the height range; omitting them returns the full history.

Step 2: Find Timestamp Entries

Look for entries where identity.contentmultimap contains the proof.basic key (use the appropriate i-address for your network). A single history entry looks like this:

{
  "identity": {
    "contentmultimap": {
      "iJvkQ3uTKmRoFiE3rtP8YJxryLBKu8enmX": [
        {
          "i4GC1YGEVD21afWudGoFJVdnfjJ5XWnCQv": {
            "version": 1,
            "flags": 13,
            "objectdata": "317d12c4...",
            "epk":        "dfb49b14...",
            "ivk":        "068aa715..."
          }
        }
      ]
    }
  },
  "blockhash": "000000000003a1b2c3d4e5f6...",
  "height": 4523891,
  "output": {
    "txid": "a1b2c3d4e5f6a7b8c9d0e1f2...",
    "voutnum": 1
  }
}

Each entry under the outer key is a DataDescriptor in public-encrypted mode (flags: 13, with epk and on-chain ivk). The payload lives in objectdata as a hex-encoded ciphertext — you'll decrypt it in the next step.

Step 3: Decrypt the Payload

Call decryptdata with the full descriptor object plus the anchoring txid, asking the daemon to retrieve and decrypt:

verus decryptdata '{
  "version":    1,
  "flags":      13,
  "objectdata": "317d12c4...",
  "epk":        "dfb49b14...",
  "ivk":        "068aa715...",
  "txid":       "a1b2c3d4e5f6...",
  "retrieve":   true
}'

The result is a descriptor whose objectdata field contains the hex-encoded utf-8 bytes of a JSON string. Convert to text and parse:

Buffer.from(result[0].objectdata, 'hex').toString('utf8')
// → '{"sha256":"a7f3b2c1...","title":"Q4 Financial Report",...}'

JSON.parse(...)
// → { sha256, title, description?, filename?, filesize? }

Compare the parsed sha256 to the hash of the document you're verifying (case-insensitive). A match confirms that this identity recorded that exact data in this transaction.

RPC requirement: decryptdata must be available on whichever Verus RPC endpoint you use. A local Verus daemon always works; some public endpoints whitelist it, others don't.

Step 4: Get Block Time

Once a payload's sha256 matches your document, fetch the entry's block to get the actual timestamp:

verus getblock "blockhash_from_history"

The time field in the block response is the Unix timestamp — the consensus-verified moment when that block was mined and your timestamp was recorded. Neither getidentityhistory nor getidentitycontent returns block time directly; it must come from the block itself via getblock (or getblockheader).

Alternative: Using getidentitycontent

getidentitycontent returns the accumulated contentmultimap for an identity and supports filtering by VDXF key. It's more ergonomic when you just want to list an identity's current timestamps — no need to iterate history entries:

verus getidentitycontent "alice@" 0 0 false 0 "vtimestamp.vrsc::proof.basic"

The trailing argument filters results to just proof.basic entries, so you get only the timestamp data. Use testidx.vrsctest::proof.basic on testnet. You'll still need decryptdata to read each entry's payload.

Important caveat: getidentitycontent's top-level blockheight and txid refer only to the identity's latest update — not to any specific entry inside the contentmultimap. If you want to know when a particular hash was recorded, you must use getidentityhistory to find the exact updateidentity transaction that added it, then call getblock against that entry's blockhash.

When to use which: use getidentitycontent for "show me this identity's current timestamps." Use getidentityhistory when you need per-timestamp recording times — which is the core of timestamp verification and what vtimestamp's own verify page does.

Writing from Your Own App

If you're building a Verus dapp that wants the same on-chain shape (public-encrypted DataDescriptor under your own outer key), here are the two envelope forms — pick the one that matches your write path. Each ends up with an identical flags: 13 entry on-chain.

Direct daemon RPC (CLI tools, MCPs, server-side workers)

Emit one entry per outer-key as an array with a { data: { message: ... } } envelope inside. The daemon resolves this shorthand on updateidentity and stores a flags: 13 DataDescriptor with epk + on-chain ivk:

{
  "yourapp.chain::outer.key": [
    { "data": { "message": JSON.stringify(payload) } }
  ]
}

Wallet writers (Verus Mobile via IdentityUpdateRequest)

Emit a single object (not an array) with messagehex. The SDK's IdentityUpdateRequestDetails.fromCLIJson shortcut detects the data key on a single object and routes it through PartialSignData → signdata-style wallet processing, which the daemon resolves to flags: 13:

{
  "yourapp.chain::outer.key": {
    "data": {
      "messagehex": Buffer.from(
        JSON.stringify(payload), 'utf-8'
      ).toString('hex')
    }
  }
}

Don't mix the two forms. Passing an array of { data: ... } through the SDK throws Unknown vdxfkey: [object Object]; passing a single object through direct daemon RPC bypasses the array-form envelope shorthand. Use the FQN form (name.chain::key) for the outer key on writes — Verus Mobile rejects custom i-address outer keys.

Reader recipe

Iterate the array under your outer key. For any descriptor where the encrypted flag (flags & 0x2) is set, call decryptdata with the full descriptor + txid + retrieve: true, then Buffer.from(result[0].objectdata, 'hex').toString('utf8') + JSON.parse to recover your payload. The wrapper key (i4GC1YGEVD21afWudGoFJVdnfjJ5XWnCQv) is the same on every chain; the outer key normalizes to its i-address on storage — readers should accept both FQN and i-address forms because that normalization isn't contractually documented.

MCP Server Integration

AI agents can interact with vtimestamp programmatically through Model Context Protocol (MCP) servers. These can be run directly via npx for testing, or configured in your AI tool's MCP settings file (e.g., Claude Desktop's claude_desktop_config.json or Cursor's MCP config).

vtimestamp-mcp (Read-only)

Allows AI agents to verify timestamps and query identity history. No wallet required.

npx vtimestamp-mcp

Provides tools for verifying timestamps, looking up identities, and reading on-chain data. Install from npm.

vtimestamp-mcp-write (Read-write)

Allows AI agents to create timestamps programmatically. Requires a local Verus wallet for signing.

npx vtimestamp-mcp-write

Provides all read tools plus timestamp creation. Install from npm.

Using vtimestamp Data

All vtimestamp data is public on the Verus blockchain. You can read and verify timestamps from your own tools, build alternative verification interfaces, or use the codebase and VDXF key schema as a reference for your own Verus projects.

  • Query any VerusID's timestamps using the VDXF keys above — no API key or permission needed
  • The VDXF key namespace (vtimestamp.vrsc::) is standardized and documented
  • The source code is MIT licensed and available on GitHub