Skip to content

Reading Sigils

Read blocks, transactions and receipts from Elysium, then parse draft envelopes.

Reading Sigils is a two-part problem. The first part, reading chain data from Elysium, works today with standard tools. The second part, recognizing and parsing Sigil envelopes within that data, follows the draft format and will change as the specification matures.

All examples use the publicClient from Getting Started.

Read blocks

TypeScript
import { publicClient } from './lib/client'

const latest = await publicClient.getBlock({ blockTag: 'latest' })
console.log(latest.number, latest.timestamp, latest.transactions.length)

const block = await publicClient.getBlock({
  blockNumber: latest.number - 10n,
  includeTransactions: true,
})

for (const tx of block.transactions) {
  console.log(tx.hash, tx.from, tx.to, tx.input.length)
}

includeTransactions: true returns full transaction objects, including input data, instead of hashes. This is what an indexer needs to inspect calldata.

Read a transaction and its receipt

The transaction holds the input data. The receipt records whether execution succeeded and which logs were emitted.

TypeScript
import type { Hash } from 'viem'
import { publicClient } from './lib/client'

const hash: Hash = '0x…'

const [transaction, receipt] = await Promise.all([
  publicClient.getTransaction({ hash }),
  publicClient.getTransactionReceipt({ hash }),
])

console.log(transaction.input) // hex-encoded calldata
console.log(receipt.status) // 'success' or 'reverted'
console.log(receipt.blockNumber, receipt.transactionIndex)
console.log(receipt.logs.length)

The pair (receipt.blockNumber, receipt.transactionIndex) is the transaction's canonical position, which determines Sigil ordering.

Follow new blocks

TypeScript
import { publicClient } from './lib/client'

const unwatch = publicClient.watchBlocks({
  includeTransactions: true,
  onBlock: (block) => {
    console.log(block.number, block.transactions.length)
  },
  onError: (error) => console.error(error),
})

// Later: unwatch()

With an HTTP transport, viem polls for new blocks. Elysium produces blocks roughly every 100 ms, so for anything beyond light monitoring, use a WebSocket provider endpoint or process blocks in ranges.

Parse a draft envelope

The parser below implements the draft encoding from Data Structure. It is not a published package, and it will change when the format is finalized.

parse-sigil.ts

Draft Example — Not Finalized

TypeScript
import { hexToBytes, type Hex } from 'viem'
import type { SigilEnvelope } from './types'

const MARKER = 'data:application/vnd.saelis+json,'
const decoder = new TextDecoder('utf-8', { fatal: true })

export function parseSigil(input: Hex): SigilEnvelope | null {
  if (input === '0x') return null

  let text: string
  try {
    text = decoder.decode(hexToBytes(input))
  } catch {
    return null // not valid UTF-8
  }

  if (!text.startsWith(MARKER)) return null

  let value: unknown
  try {
    value = JSON.parse(text.slice(MARKER.length))
  } catch {
    return null // not valid JSON
  }

  if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
  const envelope = value as Record<string, unknown>
  if (envelope.p !== 'saelis' || envelope.v !== 0) return null

  return envelope as unknown as SigilEnvelope
}

Combine them

TypeScript
import type { Hash } from 'viem'
import { publicClient } from './lib/client'
import { parseSigil } from './parse-sigil'

export async function readCandidateSigil(hash: Hash) {
  const [transaction, receipt] = await Promise.all([
    publicClient.getTransaction({ hash }),
    publicClient.getTransactionReceipt({ hash }),
  ])

  if (receipt.status !== 'success') return null

  const envelope = parseSigil(transaction.input)
  if (!envelope) return null

  return {
    id: transaction.hash,
    creator: transaction.from,
    position: { block: receipt.blockNumber, index: receipt.transactionIndex },
    envelope,
  }
}

This returns a candidate: a record that parses as a draft envelope. Whether it is a valid Sigil, and what it means under its standard, can only be decided by an indexer that applies the full rules in canonical order.