ShijimaDocs
Open app
Architecture

Failure handling

A write-ahead sender, one leader, and a resolver that settles anything a crash left behind. Proven by killing the process on mainnet.

Reviewed 2026-09-26

The write-ahead sender

A transaction is written down before it is broadcast, so after any crash the worker knows exactly what might be in flight.

Sequence

The write-ahead send

Every transaction is saved before it is broadcast, so a restart always knows what might be in flight.

Shijima
Enginea decision to actSenderoperator keyPostgresactions tableChainyour Desk1 record + action: planned2 simulate, read nonce, check gas3 signed hash + nonce: prepared4 broadcast5 sent6 receipt7 confirmed or reverted, result appended
If the process dies after step 4, the next start finds the receipt and settles it. If it dies before, the deadline in the transaction decides: once the chain’s clock passes it with no receipt, the send is marked never landed and the nonce is reused.
Read it as text
  1. Engine Postgres: record + action: planned
  2. Sender Chain: simulate, read nonce, check gas
  3. Sender Postgres: signed hash + nonce: prepared
  4. Sender Chain: broadcast
  5. Sender Postgres: sent
  6. Chain Sender: receipt
  7. Sender Postgres: confirmed or reverted, result appended

The write-ahead send

Shijima
Enginea decision to actSenderoperator keyPostgresactions tableChainyour Desk1 record + action: planned2 simulate, read nonce, check gas3 signed hash + nonce: prepared4 broadcast5 sent6 receipt7 confirmed or reverted, result appended

If the process dies after step 4, the next start finds the receipt and settles it. If it dies before, the deadline in the transaction decides: once the chain’s clock passes it with no receipt, the send is marked never landed and the nonce is reused.

Each action moves through these states:

StateMeaning
plannedThe record is saved; nothing is signed yet.
preparedSigned. Its hash, nonce and deadline are saved. The signed bytes are not.
sentBroadcast.
confirmed · revertedA receipt was found.
never_landedIt provably never made it, and its nonce can be reused.

Before signing, the sender simulates the call, reads the nonce the chain has mined, and checks the operator has enough ETH. A failure there ends the action at once as refused, with its cause, and nothing is broadcast.

The resolver

It runs when the worker starts, and before every send. For each unsettled action it looks for a receipt. With none, it decides:

SituationVerdict
Still plannednever signed
Past its on-chain deadline, plus 60 seconds for lagging nodesnever landed: the contract would now refuse it
Its nonce was used by another transactionnever landed
Anything elsewait, and send nothing new until it is settled

The deadline inside every operator call is what makes “never landed” a fact rather than a guess.

Proven on mainnet, 20 September. The worker was killed right after broadcasting a seal. On restart the resolver found it in block 68039547 and settled the record. One transaction, no duplicate: 0xa96b…1ec8.

One leader

Two workers sending with the same key would fight over nonces, and Telegram allows only one bot connection. So at start the worker takes a Postgres session advisory lock, held on one connection for its whole life. A second worker that cannot take it exits. Command-line tools that send take the same lock, so they refuse while the worker runs.

Inside the worker, one pass runs at a time; a second clock that fires meanwhile joins the pass in flight. Agents are handled one after another.

One worker per database, one worker per chain

The lock lives in the database. A second worker pointed at a different database cannot see it, and would trade the same agents with the same key. The production worker runs on the server; a local worker must never run against mainnet.

When the outside world fails

  • SERV Reasoning late, down or malformed: no decision, recorded as failed. Nothing is traded on a guess.
  • A price, feed or halt status that cannot be read: that stock is not valued and not traded, and the record names the source.
  • OpenServ unreachable: the worker's own timer keeps looking every five minutes.
  • Telegram down: messages wait in the outbox.
  • The operator low on ETH: a warning, and sends are refused before signing rather than stuck.
  • A crash mid-look: a look still marked running after 15 minutes is swept as interrupted at the next start.

The gift sender uses the same pattern: each transfer's hash and nonce are journaled before broadcast, so a retry never pays twice.

Source notes

This page was checked against the code on 2026-09-26. These are the files it follows.

On this page