Starkscan

Wallets

The calls a consumer wallet, paymaster, or portfolio client needs, mapped to the screens that use them.

Wallets

Use the Wallets lane when you are building a consumer wallet, a paymaster, or any client that renders one user's balances, activity, and transactions.

A wallet asks the chain a narrow, repetitive set of questions: what does this account hold, what happened to it, what will this transaction cost, and did it land. This page maps those questions to exact Starkscan calls, and tells you which surface to use for each.

Choose a surface

A wallet normally uses both surfaces:

  • JSON-RPCPOST https://api.starkscan.co/api/v1/SN_MAIN/rpc with X-Starkscan-Api-Key, or the compatibility node URL at https://starkscan.co/rpc/v0_10/SN_MAIN/<starkscan_api_key> when your library only accepts a URL. Header auth on /api/v1/{chain}/rpc is preferred for server clients. Use it for anything a Starknet node answers: reads at a block, fee estimation, simulation, and submitting transactions.
  • RESThttps://api.starkscan.co/v1/SN_MAIN/... with the same key. Use it for bounded wallet-state composition and indexed context a node cannot produce in one call: discovered asset candidates, a block-pinned wallet screen, an activity feed, a decoded transaction, address labels.

The rule of thumb: RPC for individual live reads and sending, wallet-state REST for a bounded home screen, and indexed REST for history and context.

First working setup

export STARKSCAN_API_KEY="YOUR_STARKSCAN_API_KEY"
export STARKSCAN_CHAIN="SN_MAIN"

Before wiring anything else, read your own limits:

curl -s https://api.starkscan.co/v1/meta/capabilities \
  -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY"

The caller block returns your scopes and live remaining budget, and rpcProvider.quotaClasses maps every RPC method to the class that governs it. Read it at startup and size your limiter from it instead of discovering ceilings through 429 responses.

Partner access and full-range certification

If you are building a wallet, wallet infrastructure or provider service, paymaster, or wallet-facing agent and need a Wallet-plan key, higher limits, or certified full-range positional event search, contact Omar. Include your product, expected routes and requests per minute, chain, event selectors, and key positions so Starkscan can issue the appropriate access and assess the coverage you need. Access and selector coverage are reviewed rather than automatically granted.

Portfolio

JobCall
Discover candidate token contractsGET /v1/{chain}/address/{address}/assets/discovery
Verify a bounded wallet screenPOST /v1/{chain}/query/wallet-state
One token balanceGET /v1/{chain}/token/{token}/balance-of/{address}
Live balance at a block tagRPC starknet_call with a balanceOf selector

Discovery returns candidate contracts and evidence, not balances. Its coverage.completeWithinScope applies only to the declared standard-fungible evidence scope; it does not prove a globally complete asset universe.

wallet-state resolves one immutable block hash and verifies a bounded set of balances plus optional nonce/class hash at that same block. The current public contract accepts at most 25 candidates. The 26-50 band remains unavailable until dedicated-pool and clean-window capacity certification plus a coordinated schema and client release. Use mode=require_complete for a wallet home screen. Any failed value produces 503 instead of a partial success. verified_partial is for diagnostics and always exposes typed failures. Starkscan never substitutes an indexed balance and never interprets an error as zero.

Treat a holding as valued only when its balance status=ok, price.status=priced, price.reasonCode=fresh_exact_cached_price, and both priceUsd and valueUsd are non-null. In verified_partial diagnostics, an unverified balance may retain a fresh unit priceUsd, but valueUsd must remain null and the client must not value that holding. When pricing is unavailable, the non-zero holding remains visible, both USD fields are null, and the holding is excluded from totalUsd; the holding is not zero dollars.

The governed current-price release covers 25 exact SN_MAIN contracts: STRK, ETH, native and bridged USDC, USDT, WBTC, two DAI contracts, canonical wstETH, xSTRK, xWBTC, BROTHER, LBTC, tBTC, strkBTC, eBTC, mRe7BTC, LINK, LORDS, EKUBO, NSTR, CASH, UNO, USN, and DOG. Address identity is authoritative; symbols are display metadata only. rETH, legacy wstETH, SolvBTC, xstrkBTC, and vSTRK remain explicitly unavailable pending reviewed conversion or identity evidence.

xtBTC remains exact-mapped for transaction-time history but is excluded from recurring current wallet pricing while its provider timestamp cannot reliably meet the 5,400-second freshness bound. It is not covered by the recurring current release, and Starkscan keeps unavailable responses explicit instead of silently widening that bound.

Branch on the typed price.reasonCode instead of guessing from nulls:

Reason codeMeaningClient action
price_metadata_unavailablePricing metadata could not be loaded for this response.Keep the holding visible and retry on the next normal refresh.
token_metadata_missingRequired token metadata is missing.Keep raw units visible; do not invent decimals or USD value.
token_unmappedNo exact provider mapping exists for the token.Keep the holding visible and unpriced; report the token if pricing is expected.
price_not_exactAvailable provider data does not meet the exact-price contract.Keep the holding unpriced; do not substitute a close match.
price_missingThe exact mapping exists, but no usable price is cached.Retry on a later normal refresh.
price_staleThe cached price is older than the allowed age.Keep the holding unpriced until a fresh price is published.
value_calculation_failedA price existed, but exact value calculation failed.Keep raw balance visible and report the request id.

A missing, empty, or unknown reasonCode is a response-contract violation, not a new client state. Retain x-request-id and report it. metadata.spamStatus is currently always not_assessed; it means no spam classification was performed and must not be interpreted as safe, trusted, or non-spam. Require walletSafe=true for the bounded selected page, then inspect coverage separately.

balance-of takes the token first and the owner second, and returns balanceRaw as a string.

Prefer wallet-state over client-side balanceOf fan-out when painting a bounded home screen. The partner spends one wallet-screen operation while Starkscan bounds and accounts for the underlying RPC work. See Migrate to wallet state.

The retired GET /v1/{chain}/address/{address}/token-holdings route always returns 410 Gone and no holdings data. Its successor links point to asset discovery and wallet state. Do not retry it or treat the SDK compatibility method as a data source.

High-cardinality wallets can require many discovery and wallet-state pages. Preserve the opaque cursor exactly, keep the same scope and first-page block hash, and continue until nextCursor is absent. Raw valuation is page-scoped. singlePageComplete means only that the walk fit in its first page; deprecated raw completeForWallet is the same alias and is not wallet-wide. Use the SDK's walletStateComplete() helper before interpreting excludedUnpricedAssets as a wallet-wide flag. Never treat a page subtotal as a complete portfolio, and never turn spamStatus=not_assessed into a client-side spam filter.

The default discovery scope is discovered_plus_registry: it checks both assets observed for the wallet and the curated registry, so it can page even for an address with no observed assets. For a home screen that intentionally shows only assets the wallet has interacted with, request scope=discovered; it avoids registry-only candidates but does not claim a globally complete asset universe.

Activity

JobCall
Account activity feedGET /v1/{chain}/address/{address}/transactions
Token movements for an accountGET /v1/{chain}/address/{address}/transfers
One transaction, decodedGET /v1/{chain}/tx/{hash}

address/{address}/transactions returns cursor-paginated list-view rows with txHash, timestampIso, kinds, counterparty, transferCount, and inline topTransfer* and operation* summaries, so a feed row does not need a second call to render.

address/{address}/transfers accepts direction=in, out, or any. Rows are newest-first by (blockNumber, txIndex, logIndex, transferIndex); preserve the exclusive cursor unchanged. Each item carries tokenAddress, fromAddress, toAddress, amount, rawValue, plus standard and tokenId, so fungible and NFT movements arrive in the same shape. For an exact mapped fungible token, historicalUsd uses the materialized transfer fact when present and may otherwise resolve a bounded causal hourly database quote for that selected row. It never calls a provider on the request path or substitutes a current quote. Pending and typed-unavailable history stays null with its reason code.

tx/{hash} returns executionStatus, finalityStatus, calldata, receipt, logs, messages, and tokenTransfers inline — enough to render "sent 5 USDC to 0x…" without a second call or a trace.

Send and gas

JobMethodQuota class
Account noncestarknet_getNoncerpc_read_state
Estimate a feestarknet_estimateFeerpc_simulation
Simulate a transactionstarknet_simulateTransactionsrpc_simulation
Submit a transactionstarknet_addInvokeTransactionrpc_write
Deploy an accountstarknet_addDeployAccountTransactionrpc_write

starknet_simulateTransactions forwards supported Starknet SIMULATION_FLAGS, including SKIP_VALIDATE and SKIP_FEE_CHARGE, to the upstream method. If you sponsor gas, size against rpc_simulation: it is the tightest class, and every sponsored transaction costs at least one estimate.

Confirmation and settlement

JobCall
Poll to finalityRPC starknet_getTransactionStatus, then starknet_getTransactionReceipt
Read at L1 settlementany read with block_id set to l1_accepted

Four block tags are accepted: latest, pending, pre_confirmed, and l1_accepted. l1_accepted resolves to the real L1 watermark rather than an alias of latest. When a product decision depends on irreversibility — withdrawals, off-ramps, credit — read at that tag instead of reimplementing settlement tracking.

Identity and labels

JobCall
Classify many addresses at oncePOST /v1/{chain}/address/intelligence
One contract's factsGET /v1/{chain}/contract/{address}
Deployment and wallet classRPC starknet_getClassHashAt

address/intelligence takes { "addresses": [...] } with up to 128 addresses per request and returns label, typeLabel, classHash, isAccount, isDeployed, deployedAtTxHash, deployedByAddress, hasReceivedFunds, and latestActivityBlock for each. Deployment fields are nullable when canonical deployment evidence is unavailable; never infer a deployer from first activity. It is the call that turns a feed of hex strings into a feed of names: resolve every counterparty on a screen in one round trip instead of 128.

This batch route requires a utility-enabled key. Check the operation's declared class and your caller scopes in /v1/meta/capabilities; treat 403 as a key-scope mismatch, not a reason to retry the same payload.

Use starknet_getClassHashAt for the pre-first-transaction deployment check and for upgrade detection.

Positions and protocol activity

JobCall
Events from one contract, filteredGET /v1/{chain}/contract/{address}/events
Events across the chainGET /v1/{chain}/events

The contract route accepts positional key filters: pass topic0 together with any of topic1 through topic15, plus numeric from_block and to_block. That lets you request only the events of a protocol that concern one user's position, instead of downloading a contract's whole event stream and filtering client-side.

Three rules to design against:

  • Filters apply to values a contract places in event keys. Events that pack their parameters into data cannot be filtered this way, which is the same constraint the underlying node has.
  • Ordinary keys are limited to 10,000 inclusive blocks when any later-position filter is present. A Wallet-plan key may exceed that limit only when eventSearch.fullRange.selectorCoverage in /v1/meta/capabilities advertises ready coverage for the exact selector and key position. The server joins the retained finalized prefix and moving head tail as a gap-free snapshot union; head.fromBlock can increase after finalization without implying missing history. openEnded and liveMaintenance do not extend that bound; check freshnessStatus, sourceHighWaterBlock, and lagBlocks for operational visibility. Coverage is dynamic; do not hard-code the currently certified selector list.
  • Wide requests still require topic0 plus explicit numeric from_block and to_block. 400 invalid_request means fix the query. 422 full_range_selector_not_supported is not retryable until capabilities advertise the selector-position pair. 503 event_key_index_coverage_unavailable means declared coverage is not ready for the requested range; honor Retry-After when present.

Sizing your rate limits

Quota classes are per minute, and each JSON-RPC child request is classified and counted independently. A batch therefore buys round trips, not headroom: a batch of 10 decrements your budget by 10.

The practical consequence: if a home screen issues N per-token starknet_call reads, your ceiling is the rpc_read_state budget divided by N screen loads per minute across the whole tenant. A bounded wallet-state request is billed as one partner operation, while internal capacity accounting still tracks actual calls and enforces the current 25-candidate contract, concurrency, timeout, and response-size ceilings.

Read the real numbers for your key from caller.rateLimit and rpcProvider.quotaClasses rather than hard-coding them.

Conventions

  • Auth header is X-Starkscan-Api-Key on both surfaces; the chain string is SN_MAIN.
  • Pagination is limit plus an opaque cursor; responses carry nextCursor. Page until nextCursor is absent.
  • Every response carries x-request-id. Budgeted responses also carry x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-policy, and x-starkscan-route-class; quote the request id when reporting a problem.
  • REST errors use { code, message, docSlug, requestId }. The x-request-id response header is canonical; the body field mirrors it when handler context is available and may otherwise be null. JSON-RPC errors are typed JSON-RPC error objects, including authentication failures, so a standard client throws instead of silently resolving undefined.
  • Amounts are raw integer strings alongside decimals. Do not parse them as floating-point numbers.

When not to start here

  • Use Quickstart when you only need a first successful request.
  • Use Starkscan RPC when your client needs a node URL and standard JSON-RPC method names.
  • Use Agents when a coding agent or tool-calling client is the consumer.
  • Use the API reference when you need every path and parameter with live try-it execution.

On this page