Get every FT transfer for a Stacks address
Fungible-token transfers are decoded and indexed, so an address's history is a filtered read. A transfer has a sender and a recipient, so you read each side with its own call. One key, one call; here is the received side:
cURL TypeScript Copy
$ curl " $SECONDLAYER_API_URL /v1/index/ft-transfers ?recipient=SP3F3GBGAR8781BF831JT4W7J5DR12ZB15A575DEC&limit=1 " \
-H " Authorization: Bearer sk-sl_… "
import { Index } from " @secondlayer/sdk ";
const index = new Index ();
const page = await index . ftTransfers . list ({
recipient : " SP3F3GBGAR8781BF831JT4W7J5DR12ZB15A575DEC ",
limit : 1 ,
});
200 response.json json Copy
{
" events ": [
{
" cursor ": " 9331956:4 ",
" block_height ": 9331956 ,
" block_time ": " 2026-10-27T04:25:47.000Z ",
" tx_id ": " 0xf230dcb1f4550142d72e6b07e5f2769c8e533c857d26b1c7dac9a62d6c252804 ",
" tx_index ": 2 ,
" event_index ": 4 ,
" event_type ": " ft_transfer ",
" contract_id ": " SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token ",
" asset_identifier ": " SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token ",
" sender ": " SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.xyk-pool-sbtc-stx-v-1-1 ",
" recipient ": " SP3F3GBGAR8781BF831JT4W7J5DR12ZB15A575DEC ",
" amount ": " 152 "
}
],
" next_cursor " : " 9331956:4 ",
" tip ": { " block_height ": 9348864 , " finalized_height ": 9348069 , " lag_seconds ": 23 },
" reorgs ": []
}
There is no single address filter: each side of a transfer is filtered on its own, so make one call per side.
const [ received , sent ] = await Promise . all ([
index . ftTransfers . list ({ recipient : address }),
index . ftTransfers . list ({ sender : address }),
]);
Copy
Merge the two lists by block_height.
A transfer to yourself is in both, so drop repeats by cursor.
A read with no fromHeight or cursor covers only the most recent ~17,280 blocks, so pass a start to go further back.
Narrow by Param One token contractId or assetIdentifierA block range fromHeight, toHeightNewest first order: "desc"
amount is a string, in the token's smallest unit.
Pass the last next_cursor back as the next cursor. It comes back null only on an empty page, which ends the loop.
import type { FtTransfersEnvelope } from " @secondlayer/sdk ";
let cursor : string | null = null ;
do {
const page : FtTransfersEnvelope = await index . ftTransfers . list ({
recipient : address ,
fromHeight : cursor ? undefined : 0 ,
cursor ,
limit : 1000 ,
});
for ( const transfer of page . events ) await save ( transfer );
cursor = page . next_cursor ;
} while ( cursor );
Copy
Pass a start height or a cursor, never both: drop the height once you hold a cursor. A cursor only resumes under the order that issued it. 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 ). Request fields to trim each row.
Every filter and the response schema: List FT transfers . The decoded feeds are described in Index .
← Previous Listen for contract events Next → Calls to a contract function Edit on GitHub