REST API and CI
A fresh testnet for every CI run: made before the tests, deleted after them.
GitHub Actions
Add an API token from your Account page as a repository secret named FORKSTATE_TOKEN, then:
.github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: foundry-rs/foundry-toolchain@v1
- id: fork
uses: Forkscout/forkstate-action@v1
with:
token: ${{ secrets.FORKSTATE_TOKEN }}
chain: base # any id from /docs/chains
# fork-block: 30000000 # pin it, so every run starts from the same state
- run: forge test --fork-url ${{ steps.fork.outputs.rpc-url }}- The testnet is deleted when the job ends, whether the tests passed or not. Set
keep: trueto leave it for debugging. - Outputs:
rpc-url,testnet-id,chain-idandfork-block. The RPC URL is also exported asFORKSTATE_RPC_URLand masked in logs. - Every testnet the action makes is tagged
CI, so they are one click to find and filter out. Add more withtags: audit, nightly. - Pin
fork-blockfor reproducible runs. The chain's cache is shared, so a pinned block is warm after the first run and costs almost nothing.
The API
Every request carries Authorization: Bearer <token>. Answers are JSON. The base URL is https://forkstate.forkscout.com/api/v1.
| Request | Does |
|---|---|
GET /testnets | Every testnet you have |
POST /testnets | Makes one: { name, chain, project?, forkBlock?, chainId?, followHead?, tags? } |
GET /testnets/:id | One testnet |
DELETE /testnets/:id | Deletes it and everything written to it |
POST /testnets/:id/clone | A copy that starts exactly where it is: { name? } |
GET /testnets/:id/snapshots | Its saved snapshots, newest first |
POST /testnets/:id/snapshots | Saves one: { name } |
POST /testnets/:id/snapshots/:sid/restore | Goes back to one |
DELETE /testnets/:id/snapshots/:sid | Deletes one |
terminal
curl -s -X POST https://forkstate.forkscout.com/api/v1/testnets \
-H "Authorization: Bearer $FORKSTATE_TOKEN" \
-H "content-type: application/json" \
-d '{"name":"nightly","chain":"ethereum","forkBlock":21000000}'
# {
# "id": "5b0e…", "name": "nightly", "chain": "Ethereum",
# "chainId": 7357001, "forkBlock": "0x1406f40", "followsHead": false,
# "rpcUrl": "https://forkstate.forkscout.com/api/rpc/…", "createdAt": "…"
# }Errors
| Status | Means |
|---|---|
| 400 | The request is malformed: no name, an unknown chain, a body that is not JSON |
| 401 | No token, or one that has been revoked |
| 402 | The account is out of credit |
| 404 | No such testnet or snapshot — or not yours, which looks the same on purpose |
| 409 | At a limit: testnets per account, or snapshots per testnet |
Every error has a body of
{ "error": "…" } with a sentence meant to be shown as it is.