# Starkscan Voyager Migration

Use this skill when a client wants to replace a bounded Voyager workflow with Starkscan REST, SDK, CLI, or MCP while keeping provider-only RPC gaps explicit.

## Contract

Given a Voyager call, map it to Starkscan only when the route is listed below or present in the published OpenAPI artifact at `/starkscan-openapi.yaml`.

If the requested Voyager surface is not listed, answer with one of these states:

- `certified`: production-safe Starkscan REST route exists.
- `beta`: usable with named clients and explicit limits.
- `provider-blocked`: keep the existing Starknet RPC provider unless Starkscan authenticated JSON-RPC access and evidence cover that workload.
- `unsupported`: no Starkscan replacement is published yet.

Do not hallucinate routes. Do not scrape Starkscan pages. Do not silently switch a REST request to RPC without telling the user.

## Environment

```bash
export STARKSCAN_CHAIN="SN_MAIN"
export STARKSCAN_API_KEY="YOUR_STARKSCAN_API_KEY"
```

Hosted REST requests use:

```text
https://api.starkscan.co/v1/$STARKSCAN_CHAIN/...
```

Set `STARKSCAN_BASE_URL` only when targeting preview or a self-hosted Starkscan deployment.

Send the key as:

```text
X-Starkscan-Api-Key: $STARKSCAN_API_KEY
```

## Migration Matrix

| Need | Starkscan replacement | State | Notes |
| --- | --- | --- | --- |
| Health / host reachability | `GET /v1/{chain}/status` | `certified` | Use first in smoke tests. |
| One block | `GET /v1/{chain}/block/{number_or_hash}` | `certified` | Use `tx_limit` when transaction list size matters. |
| Timestamp to block | `GET /v1/{chain}/block-at-timestamp?timestamp={time}&closest=before` | `certified` | Use for accounting close; response has `block.blockNumber`. Certified with exact `balance-of` for one known token/account. |
| One transaction | `GET /v1/{chain}/tx/{tx_hash}` | `certified` | Includes token transfer context when available. |
| Exact token balance | `GET /v1/{chain}/token/{token}/balance-of/{address}?block_tag={block}` | `certified` | Use exact token contract and concrete block for replayable correctness. |
| Token total supply | `GET /v1/{chain}/token/{token}/total-supply?block_tag={block}` | `certified` | Use concrete block for deterministic checks. |
| Contract metadata | `GET /v1/{chain}/contract/{address}` | `beta` | Indexed-only class/deployment/token metadata. Nullable token fields mean not identified in indexed token metadata. |
| Wallet transactions | `GET /v1/{chain}/address/{address}/transactions?limit={n}&cursor={nextCursor}` | `beta` | Cursor-based; pass `nextCursor` back unchanged. |
| Address token transfers | `GET /v1/{chain}/address/{address}/transfers?direction=any&limit={n}&cursor={nextCursor}` | `beta` | Wallet-first transfer rows across all indexed tokens. Use `direction=any`, `in`, or `out`; `any` returns either side and dedupes self-transfers, while `in`/`out` keep explicit directional views. Direction, token, block, cursor, and limit filters are applied before pagination. |
| Token transfer rows | `GET /v1/{chain}/token/{token}/transfers?address={address}&limit={n}&cursor={nextCursor}` | `beta` | Use when the token contract is already known. Do not enumerate token contracts client-side for wallet history. |
| Contract event ingestion | `GET /v1/{chain}/contract/{address}/events?limit={n}&cursor={nextCursor}` | `beta` | Voyager `/events?contract={market}&p={page}&ps=100` migrations should switch to cursor pagination. Use API `keys[]`, server-certified `decodingStatus`, and API `decodedFields`; legacy rows without `payload.keys` reconstruct only `topic0..topic3`, so record that limitation. Inspect provenance/reason fields and `eventDecodingDegraded`. |
| Wallet asset discovery | `GET /v1/{chain}/address/{address}/assets/discovery?scope=discovered_plus_registry&limit={n}` | `beta` | Returns candidates and coverage only, never balances. Follow `nextCursor`; `completeWithinScope` does not mean globally complete. |
| Wallet fungible state | `POST /v1/{chain}/query/wallet-state` | `beta` | Partner-batch route. Use `mode=require_complete`, require `walletSafe=true`, and retain the echoed immutable block hash. Errors never mean zero. |
| Retired wallet holdings | `GET /v1/{chain}/address/{address}/token-holdings` | `unsupported` | Returns typed `410` without holdings data. Migrate to discovery plus wallet-state; do not parse the retirement response as balances. |
| Contract storage read | `POST /api/v1/{chain}/rpc` with `starknet_getStorageAt` | `beta` | REST standalone storage route is not certified. Use the authenticated JSON-RPC beta path; keep existing providers for archive/history or unsupported workloads. |
| Class reads / nonce / call / estimate fee / simulation | `POST /api/v1/{chain}/rpc` | `beta` | Starkscan API-key holders may use authenticated JSON-RPC for reads, calls, fee, and simulation-envelope workflows. Strict signed simulation correctness, WSS, broad tracing, and archive/history remain separate certification lanes. |
| Already-signed invoke / declare / deploy-account writes | `POST /api/v1/{chain}/rpc` | `beta` | Available to write-scoped Starkscan API-key holders. Starkscan forwards already-signed payloads; it does not generate or custody private keys and clients must not blindly retry broadcasts. |
| WebSockets / broad Voyager parity | no current-pilot replacement | `unsupported` | Do not suggest for the current migration unless the provider track explicitly launches them. |
| Explorer transaction link | `https://starkscan.co/tx/{tx_hash}` | `certified` | UI link only; use for support/debug output. |
| Explorer contract/account link | `https://starkscan.co/contract/{address}` | `certified` | UI link only; use for accounts and contracts. |

## Contract Metadata Example

Use this only for indexed class/deployment/token identity on a known contract. Do not treat it as ABI verification and do not add request-path RPC probes.

```bash
curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/contract/0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a"
```

Expected shape:

```json
{
  "chainId": "SN_MAIN",
  "address": "0x40337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a",
  "classHash": "0x30b8...",
  "currentClassHash": "0x30b8...",
  "deploymentClassHash": "0x21a4...",
  "classHashSource": "finalized_state_diff",
  "classHashAsOfBlock": 10488302,
  "classHashFinality": "finalized",
  "deployedAtBlock": 9795720,
  "deployedAtTx": "0x...",
  "createdOnIso": "2026-05-18T00:00:00.000Z",
  "deployedByAddress": "0x...",
  "isAccount": false,
  "isToken": null,
  "tokenKind": null,
  "alias": null,
  "metadataCompleteness": true,
  "source": "indexed_read_model"
}
```

Token fields are linked: identified token contracts return `isToken: true` with a non-null `tokenKind`; non-identified contracts return `isToken: null`, `tokenKind: null`, and `alias: null`.
`classHash` is the compatibility value: it prefers `currentClassHash` and falls back to `deploymentClassHash`. `createdOnIso` is populated only by explicit deployment evidence: a canonical state update, a direct deployment fact, or a recognized factory deployment event. It remains `null` when that evidence is unavailable; first activity is never a creation date.

## Contract Events Migration Example

Use this when replacing Voyager `GET /events?contract={market}&p={page}&ps=100`. Starkscan uses cursor pagination, not page numbers or `lastPage`.

```bash
curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/contract/$MARKET_ADDRESS/events?from_block=$FROM_BLOCK&to_block=$TO_BLOCK&limit=100"
```

Continue by passing the previous response `nextCursor` back as `cursor` until `nextCursor` is absent or `null`.

Adapter mapping:

- `items` -> `items`
- `lastPage` -> stop when `nextCursor` is absent or `null`
- `keys` -> `keys` (complete canonical raw key array; `topic0..topic3` are compatibility aliases)
- `selector` -> `topic0`
- `name` -> `eventName` when attributed, otherwise `null`
- `transactionHash` -> `txHash`
- `transactionNumber` -> `txIndex`
- `number` -> `logIndex`
- `timestamp` -> Unix seconds from `timestampIso`
- `data` -> `data`
- `dataDecoded` -> `decodedFields` when `decodingStatus` is `decoded`; `decodedFieldsUnavailableReason` explains selector-only or mismatch cases

`decodingStatus` is server-certified: `decoded` has exact-schema `decodedFields`; `name_only` has an attributed name but no certified field layout; `unknown` has no attribution at the event's execution class. `eventDecodingDegraded` is an operational lookup-failure signal for the page, not an individual-event status. Keep API `keys[]` and `data[]` as authoritative evidence, while recording that legacy rows without `payload.keys` may expose only reconstructed `topic0..topic3` keys. Do not add request-time RPC, Voyager, class-ABI, or trace fallback: Starkscan's API and UI use the materialized response directly.

## Accounting Point-In-Time Balance

For a user asking "what was this account's balance for this token at a date/time?", use the accounting skill:

```bash
curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/block-at-timestamp?timestamp=2025-12-31T23%3A59%3A59Z&closest=before"
```

Then:

```bash
curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/token/<token>/balance-of/<address>?block_tag=<block.blockNumber>"
```

For `2025-12-31T23:59:59Z`, Starkscan's correctness gate verified block `5007537` against RPC.

## Adapter Rules

- Preserve cursor values exactly. Do not build cursors client-side.
- Prefer a concrete block number or block hash over `latest` for correctness-sensitive migrations.
- Treat ticker symbols as display labels only; always use the token contract address.
- Treat point-in-time accounting as one known token, one account, and one timestamp; do not infer a historical portfolio.
- Log `X-Request-Id`, rate-limit headers, HTTP status, and latency. For REST routes include `X-Starkscan-Route-Class`; for RPC calls include `X-Starkscan-Rpc-Class`.
- Honor `Retry-After` on `429` and `503`.
- For route status and schema, read `/starkscan-openapi.yaml` before generating client code.

## Smoke Test

```bash
curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/status"

curl -sS \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
  "${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/block-at-timestamp?timestamp=2025-12-31T23%3A59%3A59Z&closest=before"
```

If both return JSON, migrate one workflow at a time and write down the Starkscan route state next to each replaced Voyager call.
