One-Time-View Wallet

Case · TON / crypto infrastructure · 2026

Two separate services - test and combat - generate TON-wallets: 24-word mnemonics, ed25519-pair, address V5R1. The Sid-phrase comes back exactly once in a wallet’s lifetime and never appears again – neither in the API, nor in the panel, nor in the database.

2 networkstestnet and mainnet
24 wordswallet mnemonics
1 displaySidphrase in the first answer only
0 secretsservice-sharing

In short: Mnemonics can be collected within 15 minutes, but only seen once. Testnet and mainnet operate as two isolated services, so compromising one does not open access to the other.

The challenge

Internal services need real TON wallets for testing - without going to a real wallet and without the risk of mixing test keys with combat ones. And where there is real money on the other end, you need a wallet with the same logic, but without the right to error: a compromised mnemonic is not a bug, but a direct loss.

The solution is two physically different services on neighboring domains with the same architecture, but without a single shared secret. ton.ifreework.com for testing mainnet.ifreework.com for combat wallets. Compromising one does not give access to the other.

One wallet path

Generation never happens inside a web query – only through a queue, so that a slow external call can’t take the workman and put the site down.

  1. Signed request

    Each call carries a HMAC-SHA256 signature on the canonical line (method, path, time, nonce, body hash) and a mandatory Idempotency-Key Repeating the same key returns the already created wallet, and does not produce a second one.

    X-Signature · X-Nonce · X-Timestamp
  2. Standing in line

    POST He responds instantly, without expecting anything: there is no wallet yet. id status pending And the address for the survey.

    202 Accepted · queue: ton-wallets
  3. Generation in the Node sidecar

    A separate queue worker calls Node service with TON SDK – all cryptography is isolated there because TON SDK is written in JavaScript, not PHP. The sidecar only listens to the internal docker network and requires a shared token.

    Node 22 · @ton/crypto · @ton/ton
  4. Status survey

    The client is interviewing the same iduntil the status becomes ready or failed - usually less than a second. A wallet from someone else's request for this id inaccessible: alien id return 404, as well as non-existent.

    GET /api/.../{id}
  5. Disclosure is all.

    Mnemonic is only found in the first successful response to a survey. The next request - at least in a second - will return the same wallet with the mnemonic: null. Not taken for 15 minutes - the phrase is erased from the temporary cache irrevocably, the address remains, the key is not.

    RevealCache · TON_REVEAL_TTL

Testnet and mainnet are twins without shared secrets.

Combat service is not a test mode, but a complete clone with a reflashed network: your container, your base, your sidecar, your domain. Nothing is overused.

ton.ifreework.com — testnet

  • Global id network -3He's sewn in the sidecar dead.
  • Addresses begin with 0Q… / kQ…
  • Storage mnemonics: by default not stored, optionally encrypted
  • Cleaning of overdue records - once every 5 minutes, storage up to a year

mainnet.ifreework.com - combat

  • Global id network -239He's sewn in the sidecar dead.
  • Addresses begin with UQ… / EQ…
  • Storage of mnemonics is prohibited at the configuration level - not just turned off by default
  • Clearing expired records – once per minute, storing 180 days

The client on each side also double-checks himself: if the answer suddenly contains an address of the wrong format - UQ… where it should be 0Q…The service refuses to accept it, instead of silently continuing to work with mixed networks.

Incident: how the site was laid five workmen

The real reason is the synchronous call of the Node sidecar inside the php-fpm-worker with a pool of 5 workmen: one slow request, along with a scanner-bot, knocked the site at the same time, took all the workmen at once - and laid down the entire site, including the creation of wallets.

Soon after the first depletion, the creation of wallets began to fall. Logs were silent - but not because everything was fine: directive access_log $ton_log_path … In the configuration, nginx checks for the literal text of the directive, not for what the variable is disclosed in, so nginx substituted before each path. /etc/nginx/ I couldn't write anywhere. Logistics have been silently broken since the deployment and masked the real problem.

The real problem was synchronous sidecar call inside a web request for a default pm.max_children: 5. The fix is not just to increase the pool, but to remove generation from the web query path: it has moved into a separate queue process that cannot be depleted by web traffic, regardless of the size of the php-fpm pool.

What keeps the system on its feet

Sid-phrase not in the logs

The secret object does not store anything in its fields, the individual handler cleans the channel, and the API endpoint does not log the query body.

No dangerous request history

Telescope and Pulse on these services are not installed in principle, and not just turned off in configuration.

Alien ID can not be overcome

ID is verified after HMAC- or session authentication, so an unauthorized response does not disclose the existence of the wallet.

IP cannot be faked

Nginx recovers real address via Cloudflare and docker hops and resets incoming inboxes X-Forwarded-*.

Personalized access

Accounts are created manually by one team, there is no common password, each wallet is fixed created_by.

There is no public API

Caddy, nginx and the application itself independently return 404 for external requests. /api/*.

Architecture

Technology

  • Backend — Laravel 13, PHP 8.4
  • queue - Redis, separate queue:workprocess
  • sidecar — Node 22, @ton/crypto, @ton/ton
  • Warehouse MySQL, Wallet Metadata and Audit

Safety and operation

  • Authentication HMAC-SHA256, nonce, time window, idempotence
  • Network Private channel via 127.0.0.1/VPN
  • Panel - username, QR-code, export CSV
  • Services Planned cleaning of expired records

The result

Internal services receive one-time test wallets without the risk of hooking combat keys, and where real money is at stake, the same proven mechanism on a completely isolated service: its network, its database, its secrets. Sid-phrases exist in the open form exactly one answer of one query - before and after it simply does not exist.

Read more about the service of creating and binding TON-wallets

Laravel 13 · PHP 8.4 · Node 22 · Redis · MySQL · TON SDK · HMAC-SHA256