Starknet Subsquid Network API
The Starknet API of Subsquid Network is currently in beta. Breaking changes may be introduced in the future releases.
Subsquid Network API distributes the requests over a (potentially decentralized) network of workers. The main gateway URL now points at a router that provides URLs of workers that do the heavy lifting. Each worker has its own range of blocks that it serves. The recommended data retrieval procedure is as follows:
- Retrieve the dataset height from the router with
GET /height
. - Query the router for an URL of a worker that has the data for the first block of the relevant range with
GET /${firstBlock}/worker
. - Retrieve the data from the worker with
POST /
, making sure to set the"fromBlock"
query field to${firstBlock}
. - Exclude the received blocks from the relevant range by setting
firstBlock
to the value ofheader.number
of the last received block plus one. - Repeat steps 2-4 until all the required data is retrieved.
The main URL of the Starknet gateway is
https://v2.archive.subsquid.io/network/starknet-mainnet
Unlike in the explorer, addresses in this API do not have leading zeros (both in valid requests and in the returned data). For example, explorer's 0x00ce6c...0552
becomes 0xce6c...0552
. This is the format that Starknet RPC nodes use.
Implementation examples:
Manually with cURL
Suppose we want data on all txs sent by Layerswap
/0x19252b1deef483477c4d30cfcc3e5ed9c82fafea44669c182a45a01b4fdb97a
starting from block 600_000. The steps are:
-
Verify that the dataset has reached the required height:
curl https://v2.archive.subsquid.io/network/starknet-mainnet/height
Output
632494
-
Get a worker URL
curl https://v2.archive.subsquid.io/network/starknet-mainnet/600000/worker
Output
https://rb03.sqd-archive.net/worker/query/czM6Ly9zdGFya25ldC1tYWlubmV0
-
Retrieve the data from the worker
curl https://rb03.sqd-archive.net/worker/query/czM6Ly9zdGFya25ldC1tYWlubmV0 \
-X 'POST' -H 'content-type: application/json' -H 'accept: application/json' \
-d '{
"type": "starknet",
"fromBlock":600000,
"toBlock":632494,
"fields":{"transaction":{"transactionHash":true}},
"transactions":[{"senderAddress":["0x19252b1deef483477c4d30cfcc3e5ed9c82fafea44669c182a45a01b4fdb97a"]}]
}' | jqOutput:
[
{
"header": {
"number": 600000,
"hash": "0x898fe7f61f5d662199d223de496988f221d150ed054f2fe5e681b2988b9e2c"
},
"transactions": []
},
{
"header": {
"number": 600007,
"hash": "0x44aa251cee1baaf3f19accefd223ce5208815686c881bf645ffb3e3348a5ddc"
},
"transactions": [
{
"transactionIndex": 24,
"transactionHash": "0x6a88edb0713769de4ad4d450df70911c3e9a7e8253c135c9574d0b3542ced18"
}
]
},
...
{
"header": {
"number": 617979,
"hash": "0x7f6a8516a91eefa6a65972c47002cbe3851e3c4287f670c914850960d29ca29"
},
"transactions": []
}
] -
Observe that we received the transactions up to and including block 617979. To get the rest of the data, we find a worker who has blocks from 617980 on:
curl https://v2.archive.subsquid.io/network/starknet-mainnet/617980/worker
Output:
https://lm04.sqd-archive.net/worker/query/czM6Ly9zdGFya25ldC1tYWlubmV0
We can see that this part of the dataset is located on another host.
-
Retrieve the data from the new worker
curl https://lm04.sqd-archive.net/worker/query/czM6Ly9zdGFya25ldC1tYWlubmV0 \
-X 'POST' -H 'content-type: application/json' -H 'accept: application/json' \
-d '{
"type": "starknet",
"fromBlock":617980,
"toBlock":632494,
"fields":{"transaction":{"transactionHash":true}},
"transactions":[{"senderAddress":["0x19252b1deef483477c4d30cfcc3e5ed9c82fafea44669c182a45a01b4fdb97a"]}]
}' | jqOutput is similar to that of step 3.
-
Repeat steps 4 and 5 until the dataset height of 632494 reached.
In Python
def get_text(url: str) -> str:
res = requests.get(url)
res.raise_for_status()
return res.text
def dump(
gateway_url: str,
query: Query,
first_block: int,
last_block: int
) -> None:
assert 0 <= first_block <= last_block
query = dict(query) # copy query to mess with it later
dataset_height = int(get_text(f'{gateway_url}/height'))
next_block = first_block
last_block = min(last_block, dataset_height)
while next_block <= last_block:
worker_url = get_text(f'{gateway_url}/{next_block}/worker')
query['fromBlock'] = next_block
query['toBlock'] = last_block
res = requests.post(worker_url, json=query)
res.raise_for_status()
blocks = res.json()
last_processed_block = blocks[-1]['header']['number']
next_block = last_processed_block + 1
for block in blocks:
print(json.dumps(block))
Full code here.
Router API
GET
/height
(get height of the dataset)
Example response: 632494
.
GET
${firstBlock}/worker
(get a suitable worker URL)
The returned worker will be capable of processing POST /
requests in which the "fromBlock"
field is equal to ${firstBlock}
.
Example response: https://rb06.sqd-archive.net/worker/query/czM6Ly9zdGFya25ldC1tYWlubmV0
.
Worker API
POST
/
(query logs and transactions)
Query Fields
- type:
starknet
. - fromBlock: Block number to start from (inclusive).
- toBlock: (optional) Block number to end on (inclusive). If this is not given, the query will go on for a fixed amount of time or until it reaches the height of the dataset.
- includeAllBlocks: (optional) If true, the Network will include blocks that contain no data selected by data requests into its response.
- fields: (optional) A selector of data fields to retrieve. Common for all data items.
- transactions: (optional) A list of transaction requests. An empty list requests no data.
- events: (optional) A list of event requests. An empty list requests no data.
Example Request
{
"type": "starknet",
"fromBlock":632000,
"toBlock":632494,
"fields": {
"block": {
"timestamp": true
},
"event": {
"keys": true,
"data": true
},
"transaction": {
"transactionHash":true
}
},
"events": [
{
"fromAddress": [
"0x19252b1deef483477c4d30cfcc3e5ed9c82fafea44669c182a45a01b4fdb97a"
],
"transaction": true
}
]
}
Example Response
[
{
"header": {
"number": 632000,
"hash": "0xdfebe2b6af20dfe7f27d5fe8b1b4e8ee48ad812ce9bfd9c756c9db7dbcdb22",
"timestamp": 1712950160
},
"transactions": [
{
"transactionIndex": 110,
"transactionHash": "0x151fa3c8633e6ed71301af4afc8f73a141ef39cca1d51d0f72d66a11911e2f3"
},
{
"transactionIndex": 306,
"transactionHash": "0x7e1c307624c5c78e311e2a08f0355dfef80e6fc6ed47c64ceda757e044f2c85"
}
],
"events": [
{
"transactionIndex": 110,
"eventIndex": 1,
"keys": [
"0x1dcde06aabdbca2f80aa51392b345d7549d7757aa855f7e37f5d335ac8243b1",
"0x151fa3c8633e6ed71301af4afc8f73a141ef39cca1d51d0f72d66a11911e2f3"
],
"data": [
"0x1",
"0x1",
"0x1"
]
},
{
"transactionIndex": 306,
"eventIndex": 1,
"keys": [
"0x1dcde06aabdbca2f80aa51392b345d7549d7757aa855f7e37f5d335ac8243b1",
"0x7e1c307624c5c78e311e2a08f0355dfef80e6fc6ed47c64ceda757e044f2c85"
],
"data": [
"0x1",
"0x1",
"0x1"
]
}
]
},
...
{
"header": {
"number": 632492,
"hash": "0x440ee029f2a970b2546eb39ab23075659ec8e0246c94f62d21e21f912dfb58d",
"timestamp": 1713049189
},
"transactions": [
{
"transactionIndex": 125,
"transactionHash": "0x61bd3d233cfe9cb387f3b016127ffb0d66d265c2593f80a317404e2f3c334bb"
}
],
"events": [
{
"transactionIndex": 125,
"eventIndex": 1,
"keys": [
"0x1dcde06aabdbca2f80aa51392b345d7549d7757aa855f7e37f5d335ac8243b1",
"0x61bd3d233cfe9cb387f3b016127ffb0d66d265c2593f80a317404e2f3c334bb"
],
"data": [
"0x1",
"0x1",
"0x1"
]
}
]
},
{
"header": {
"number": 632494,
"hash": "0x2782c5ca3f1d3eb2e4c085fc17908b9b86bfe91807cd452374bcb40b2245925",
"timestamp": 1713049569
},
"transactions": [],
"events": []
}
]
Data requests
Transactions
{
contractAddress: string[],
senderAddress: string[],
type: string[],
firstNonce: int,
lastNonce: int,
events: boolean
}
A transaction will be included in the response if it matches all the requests. An empty array matches no transactions; omit all requests to match all transactions.
All events emitted by the selected transactions will be included into the response if events
is set to true
.
See Data field selector for info on field selection.
Events
{
fromAddress: string[],
key0: string[],
key1: string[],
key2: string[],
key3: string[],
transaction: boolean
}
An event will be included in the response if it matches all the requests. An empty array matches no events; omit all requests to match all events.
If transaction
is set to true
, all parent transactions will be included into the response.
See Data field selector for info on field selection.
Data fields selector
A selector of fields for the returned data items. Its structure is as follows:
{
block: // field selector for blocks
transaction: // field selector for transactions
event: // field selector for events
}
Block fields
{
parentHash
status
newRoot
timestamp
sequencerAddress
}
A valid field selector for blocks is a JSON that has a subset of these fields as keys and true
as values, e.g. {"status": true, "timestamp": true}
.
Transaction fields
{
transactionHash
contractAddress
entryPointSelector
calldata
maxFee
type
senderAddress
version
signature
nonce
classHash
compiledClassHash
contractAddressSalt
constructorCalldata
}
A valid field selector for transactions is a JSON that has a subset of these fields as keys and true
as values, e.g. {"transactionHash": true, "type": true, "calldata": true}
.
Event fields
{
fromAddress
keys
data
}
A valid field selector for logs is a JSON that has a subset of these fields as keys and true
as values, e.g. {"fromAddress": true, "data": true}
.