.md

Decode Clarity print events

You don't decode them: every print is indexed already parsed from Clarity into JSON, with the raw hex kept beside it. One key, one call:

$curl "$SECONDLAYER_API_URL/v1/index/events?event_type=print&contract_id=SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry&order=desc&limit=1" \
-H "Authorization: Bearer sk-sl_…"
200response.jsonjson
{
"events": [
{
"cursor": "9348740:2",
"block_height": 9348740,
"block_time": "2026-10-28T17:42:12.000Z",
"tx_id": "0xfff08aecc097d418f55b98b10abe2bc6b1785b89b0ed1b80506fc15a375a14f9",
"tx_index": 0,
"event_index": 2,
"event_type": "print",
"contract_id": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry",
"payload": {
"topic": "print",
"value": {
"amount": "40477",
"block-height": "973577",
"max-fee": "850",
"recipient": { "hashbytes": "0x98cfcc0a22d16a9ce131dd774ee0245a3b10dcfb", "version": "0x04" },
"request-id": "4636",
"sender": "SPEG24N6F52VPD8001H20TD317GHDD23GGVEZC56",
"topic": "withdrawal-create"
},
"raw_value": "0x0c0000000706616d6f756e74…"
}
}
],
"next_cursor": "desc:9348740:2",
"tip": { "block_height": 9348932, "finalized_height": 9348069, "lag_seconds": 18 },
"reorgs": []
}

The decoded tuple is value: uints are decimal strings, buffers are hex, principals are strings. raw_value is the hex you started from, kept so you can check the decode.

A contract prints different shapes under different topics. The print-schema call lists a contract's topics and each field's Clarity and TypeScript type.

$curl "$SECONDLAYER_API_URL/v1/index/contracts/SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry/print-schema" \
-H "Authorization: Bearer sk-sl_…"
200response.jsonjson
{
"contract_id": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry",
"topics": [
{
"topic": "withdrawal-create",
"count": 29,
"fields": [
{ "name": "amount", "camel_name": "amount", "clarity_type": "uint", "ts_type": "bigint" },
{ "name": "recipient", "camel_name": "recipient", "clarity_type": "(tuple (hashbytes (buff 32)) (version (buff 1)))", "ts_type": "{ hashbytes: string; version: string }" },
]
},
],
"sampled": true
}

Fields come back with their Clarity type, so you know recipient is a tuple before you parse it. The schema is sampled from indexed prints, so sampled: true means older topics may be missing.

A uint is a bigint in a subgraph handler (ts_type), but a decimal string in REST values.

The events feed filters by contract, not by topic. Keep the topic you want from the tuple's own topic key.

const withdrawals = page.events.filter(
(event) => (event.payload.value as { topic?: string } | undefined)?.topic === "withdrawal-create",
);
  • Prints that aren't tuples have no topic key; the schema lists them under *.
  • To be pushed one topic instead of reading it, a webhook trigger takes topic (Print fields).
  • Page with next_cursor, as in any decoded feed.

Hosted reads are metered, with a free monthly allowance (What it costs). Reading a contract's prints means reading every topic it emits, so a chatty contract costs more rows than the topic you keep.

Every filter and the response schema: Decoded events by event_type and Print payload schemas. The shapes by contract: Print schemas.

Edit on GitHub