.md

List every call to a Stacks contract function

Every contract call is decoded and indexed, so one function's history is a filtered read. Each row carries its arguments already parsed from Clarity. One key, one call:

$curl "$SECONDLAYER_API_URL/v1/index/contract-calls?contract_id=SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token&function_name=transfer&from_height=9340000&limit=1" \
-H "Authorization: Bearer sk-sl_…"
200response.jsonjson
{
"contract_calls": [
{
"cursor": "9341326:0",
"block_height": 9341326,
"block_time": "2026-10-28T00:52:36.000Z",
"tx_id": "0x385726bb0fe44cac3c127096a28ddd3c5aba20e45e7bb7d09cbba15b9b579d9e",
"tx_index": 0,
"contract_id": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token",
"function_name": "transfer",
"sender": "SP2X2S5J0DXXRBR786CVVT15SWZT9T6E5D5YYXXF1",
"status": "success",
"args": ["10514538", "SP2X2S5J0DXXRBR786CVVT15SWZT9T6E5D5YYXXF1", "SP1SYS7ZHE8Z05WGJYF7EHXCYCXSJY0FW75MT7M9Z", null],
"result": true,
"result_hex": "0x0703"
}
],
"next_cursor": "9341326:0",
"tip": { "block_height": 9348931, "finalized_height": 9348069, "lag_seconds": 18 },
"reorgs": []
}

args holds the call's arguments in order, one entry per parameter, as plain JSON. Read the order and names from the contract's ABI: transfer takes the amount, sender, recipient, then an optional memo.

A read with no from_height or cursor covers only the most recent ~17,280 blocks, so pass a start to go further back.

Narrow byParam
One functionfunctionName
Who called itsender
Several contractscontractId as a list (max 20)
Every contract of a standardtrait, instead of contractId
A block rangefromHeight, toHeight
  • There is no status filter: failed calls come back too, so check each row's status.
  • Only calls a transaction makes directly are listed.
  • Reads run oldest first; there is no order parameter on this feed.

Pass the last next_cursor back as the next cursor. It comes back null only on an empty page, which ends the loop.

let cursor: string | null = null;
do {
const page = await index.contractCalls.list({
contractId,
functionName: "transfer",
fromHeight: cursor ? undefined : 0,
cursor,
limit: 1000,
});
for (const call of page.contract_calls) await save(call);
cursor = page.next_cursor;
} while (cursor);

Pass a start height or a cursor, never both: drop the height once you hold a cursor. For a one-off export, walk() does the paging for you; it keeps no checkpoint (list, walk, consume).

Hosted reads are metered, with a free monthly allowance (What it costs). A busy function means many rows, so narrow by height before you page.

Every filter and the response schema: Decoded contract-call transactions. The decoded feeds are described in Index.

Edit on GitHub