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
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.
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
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.
Draft Example — Not Finalized
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
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.