Choosing and sizing the TON endpoint

How TON_NETWORK picks the RPC and key pair, what each deposit poll, background scan, balance read and withdrawal costs in Toncenter requests, and the back-off ladder that ends in a stopped monitor.

6 min readUpdated 6 August 2026rpc, toncenter, rate-limits, api-key, ton-network

TON is the chain on this platform most likely to be throttled. Every deposit poll, every background scan, every balance read and every step of every withdrawal goes through one HTTP endpoint, and the default that endpoint resolves to — anonymous toncenter.com — allows roughly one request per second for the whole install, shared across every user.

Nothing in the TON service paces itself against that. This page is about choosing the endpoint, sizing it, and recognising what running out looks like.

The endpoint pair

TON_NETWORK selects one of two pairs. It is not a general network selector — it is a two-way switch, and only one value is special.

TON_NETWORK RPC variable Key variable Default endpoint
exactly testnet TON_TESTNET_RPC TON_TESTNET_RPC_API_KEY https://testnet.toncenter.com/api/v2/jsonRPC
anything else, including unset TON_MAINNET_RPC TON_MAINNET_RPC_API_KEY https://toncenter.com/api/v2/jsonRPC

The comparison is a literal string equality against "testnet". Testnet, TESTNET, test, a typo, an empty value and an absent value all select mainnet, and none of them raises an error or writes a log line.

Two-value switch. The literal string "testnet" selects the testnet RPC and key; every other value selects mainnet. No validation, no error.
Toncenter-compatible JSON-RPC endpoint used on mainnet. Drives deposit polling, balance reads, withdrawal signing and withdrawal confirmation.
Toncenter API key for mainnet, sent as the X-API-Key header on every call.
Toncenter-compatible JSON-RPC endpoint used when TON_NETWORK is exactly "testnet".
Toncenter API key for testnet, sent as the X-API-Key header.

The requirements console trims TON_NETWORK before comparing it. The TON service does not. So TON_NETWORK=" testnet" makes the diagnostics probe the testnet endpoint and report it as healthy, while every real deposit poll and withdrawal runs against mainnet.

The console also grades the value itself: anything other than mainnet or testnet is shown as an invalid value even though the runtime silently accepts it as mainnet. Read that line rather than assuming the blank is a default.

The API key is optional in the code and required in practice

The key is attached by the HTTP provider as an X-API-Key header on every request. Nothing refuses to start without it. At service construction the backend logs a single line under the TON tag:

No TON API Key provided. Some functionalities may be limited.

That is the only warning you get at runtime. The diagnostics are blunter about it. Run Admin → Ecosystem → Blockchains → Requirements → TON and the live probe names the condition in its own label:

Runs the live read-only probe suite for one chain and maps the results onto deposit and withdrawal readiness
What the probe shows Meaning
Toncenter getMasterchainInfo + API key A key is configured and was sent
Toncenter getMasterchainInfo — NO KEY (anonymous, ~1 req/s) No key; the check passes as a warning, not a success
masterchain seqno <n> The endpoint answered and is live
HTTP 401 — invalid API key The key is wrong, not missing
HTTP 429 — rate limited You are already over quota during a single probe

The report also carries an explicit warning string when the key for the active network is unset:

No TON_MAINNET_RPC_API_KEY — toncenter anonymous rate limits (~1 req/s) will
throttle deposit polling and withdrawal confirmation.

What each flow costs

Every number below is a JSON-RPC request against your endpoint. Nothing in the TON service batches or caches them, and the service has no client-side rate limiter of its own — the only pacing anywhere is the background scanner's token bucket and the withdrawal queue's per-chain cooldown.

Flow Requests Cadence
Live deposit poll, per watched address 1 getTransactions (10 most recent, archival), plus 1 more for each new transaction it decides to credit Every 60 seconds while the user has the deposit page open
Background scan pass, per address Same shape: 1 getTransactions, plus 1 per new transaction Every ECOSYSTEM_SCAN_INTERVAL_MS (default 120 s), paced by a 0.5 requests/second bucket for TON
Transaction list for a TON address 1 getTransactions Per view. Not cached — the Redis cache in the transaction fetcher covers the EVM path only
Master wallet balance 1 getAddressBalance per TON master wallet Every load of the master wallet list or detail screen; uncached, with a 10-second global timeout on the list
One withdrawal 1 runGetMethod (seqno) + 1 getAddressBalance + 1 sendBoc + 1 to 10 getTransactions (5 most recent) The confirmation polls are 10 seconds apart, so a slow one occupies the endpoint for about 100 seconds

A withdrawal therefore costs between four and thirteen requests, and the withdrawal queue enforces a 5-second cooldown between withdrawals on the same chain specifically so a burst does not trip the quota.

Per-second scan budget for TON in the background scanner's token bucket. One "request" here is one address scan pass. Raise it only with a paid plan or your own endpoint.
How often each address in the working set is re-scanned, in milliseconds. Applies to every chain.
How long an address stays in the background scanner's working set after its last deposit-page visit, in milliseconds. The default is 72 hours.
Set to "false" to turn off the background deposit scanner for every chain. Session monitors keep running.

Lowering ECOSYSTEM_SCAN_ACTIVE_TTL_MS is the safest lever: it reduces load in direct proportion to how many idle addresses you stop watching. Raising ECOSYSTEM_SCAN_RATE_TON is the one that bites, because the quota you are spending is shared with the live monitors and with every withdrawal in flight.

The failure ladder

Throttling does not announce itself. It arrives as errors inside loops that absorb them, and the loops give up in a particular order.

The live deposit monitor backs off, then stops. It counts consecutive polling errors. The next poll is scheduled at 60 s × 2^min(errors, 4), so the interval walks 2, 4, 8 and then 16 minutes and stays there. At ten consecutive errors the monitor deletes itself and logs:

Too many consecutive errors for <address>, stopping deposit monitoring

Add the delays up and a dead or throttled endpoint gets roughly 110 minutes before that address stops being watched at all. The monitor does not resume on its own — the user has to reopen the deposit page. One success anywhere in the sequence resets the counter and the interval back to 60 seconds.

The background scanner does not stop. A failed pass is logged as a warning and the address is simply rescheduled. This is why deposits often keep crediting, late, on an endpoint that has already killed every live monitor.

Withdrawal confirmation is where it costs money. The transfer is broadcast before the confirmation polling begins. Ten failed polls and the handler throws, the row is marked FAILED, and the queue refunds the user — with the coins already on-chain. That is the expensive failure and it is covered in full in Withdrawals and fees.

The withdrawal handler asks the wallet contract for its sequence number, and that call swallows every error — a throttled read, a timed-out read and a genuinely undeployed contract are indistinguishable. All three come back as null, which the handler treats as 0.

Seqno 0 is only correct for a wallet that has never sent anything. For an address that has withdrawn before, the message is signed against the wrong sequence number, the wallet contract will not accept it, and the withdrawal then falls into the confirmation-timeout path above.

Replacing toncenter

You are not tied to toncenter.com. The integration uses only the v2 jsonRPC surface, and only five methods:

Method Used by
getTransactions Deposit polling, background scanning, transaction lists, withdrawal confirmation
getAddressBalance Master wallet balances, and the pre-flight balance check on every withdrawal
runGetMethod Reading a wallet contract's sequence number
sendBoc Broadcasting the signed withdrawal
getMasterchainInfo The admin diagnostics probe only

Anything that speaks that surface at the URL you configure will work — a paid Toncenter plan, another hosted provider, or your own ton-http-api in front of your own liteserver. Point TON_MAINNET_RPC at its /api/v2/jsonRPC path and put its key, if it has one, in TON_MAINNET_RPC_API_KEY.

Deposit polling requests transactions with the archival flag set. Whatever you point the platform at has to be backed by an archival node, or that call — the one every deposit depends on — will not return the history it is asking for. The withdrawal confirmation poll does not set the flag.

Validate the swap before you trust it:

  1. Edit .env with the new URL and key.

  2. Restart both processes — see below.

  3. Run the diagnostics. Admin → Ecosystem → Blockchains → Requirements → TON → run the test. You want the RPC check reporting masterchain seqno with a latency you are happy with, and no NO KEY label.

  4. Read the readiness rows, not the ticks. Deposits and Withdrawals each get their own row. A healthy endpoint with a locked vault still reports Withdrawals as failed.

  5. Move real coins. Deposit a small amount, watch it credit, withdraw a smaller amount back out and confirm the row reaches COMPLETED with a hash.

Changing the endpoint needs a restart — of two processes

The TON service is a per-process singleton that reads TON_NETWORK, the RPC URL and the API key once, when it is first constructed, and never again.

Two processes construct it. The API host runs the live deposit monitors and signs the withdrawals users submit; the scheduler process runs the background deposit scanner and the pending-withdrawal recovery sweep, which signs too. Restart both:

pm2 restart backend
pm2 restart cron

Restarting only backend leaves the scanner talking to the old endpoint, which produces the confusing shape where live monitoring moves to the new provider and background detection does not.

Change Restart needed
TON_NETWORK, either RPC URL, either API key Yes, both processes
ECOSYSTEM_SCAN_RATE_TON, ECOSYSTEM_SCAN_INTERVAL_MS, ECOSYSTEM_SCAN_ACTIVE_TTL_MS Yes — the scanner reads them at module load
Enabling the chain row No — a service that is currently inactive re-checks the row on its next use
Disabling the chain row Yes, both processes — once the service has gone active the row is never read again
Enabling the token row, editing fee or precision No

The two directions are not symmetrical. The service caches its active flag and only re-reads the chain row while that flag is false, so switching TON on is picked up within one deposit poll, balance read or withdrawal. Switching it off is not: there is no per-request check anywhere on the deposit or withdrawal path, so a process that has already seen TON as active keeps polling deposits, reading balances and signing withdrawals against a chain the admin screen shows as disabled. Restart backend and cron to make a disable stick.