An automated executor can make a valid decision and still have no safe action to take.
The awkward interval is between deciding to submit a transaction and learning whether the chain included it. During that interval, permissions can disappear, processes can crash, RPC requests can time out, and retries can create a second execution.
Treating all of this as a generic pending status hides the information needed for safe recovery. A useful executor distinguishes at least three states:
DECIDED_UNSENT -> SENT_UNMINED -> CONFIRMED | REVERTED
| |
v v
ABANDONED UNCERTAIN
UNCERTAIN is not another spelling of pending. It means the executor lacks enough evidence to act automatically.
Decided but unsent
In this state, the executor has chosen an action and recorded that decision, but nothing has left the machine.
This is the easy case operationally because abandoning the action has no chain effect. If the relevant permission disappears before submission, the executor can mark the decision abandoned and stop.
Before making any external call, persist enough information to explain and reproduce the decision:
- An immutable intent ID
- The account and chain
- The exact proposed action
- The policy or configuration version used
- The decision timestamp
- The observed input data or a reference to it
- The permission state observed during evaluation
- A terminal reason if the intent is abandoned
Do not overwrite an abandoned intent with a newer decision. Create another intent. An append-only history makes it possible to distinguish “we chose not to send” from “we lost track of the send.”
The executor may re-check whether submission is still allowed, but that check does not create an atomic boundary across a database, signer, RPC provider, and chain. If the permission check succeeds and then the process crashes, recovery still needs to know whether a request left the machine.
That requires a write-ahead submission record.
Persist before the network call
Once a transaction has been constructed and signed, its raw bytes and transaction hash should be persisted before broadcasting when the signing setup permits that. The hash is derived locally, so the executor does not need an RPC response to identify the transaction.
A submission record might contain:
{
"intent_id": "intent-123",
"attempt": 1,
"status": "submission_prepared",
"chain_id": 8453,
"nonce": 42,
"transaction_hash": "0x...",
"signed_payload": "0x..."
}
If an external signing or relay service does not expose the signed payload, persist an idempotency key and provider request ID before the call. This is weaker because recovery now depends on the provider’s idempotency and lookup semantics. That limitation should be explicit in the design rather than hidden behind a retry loop.
The key rule is simple: the durable record must advance before the next irreversible external interaction.
Sent but unmined
A transaction is sent but unmined when the executor has evidence of submission but no inclusion receipt.
The executor may observe. It may query multiple RPC providers, inspect the account nonce, rebroadcast the identical signed payload, or ask a relay service about the original request ID. It must not casually construct a fresh transaction.
Rebroadcasting identical signed bytes is generally different from creating another transaction. The identical payload has the same hash and nonce. A newly built payload may have a new nonce, different fee fields, or altered call data, making a second execution possible.
This distinction becomes especially important with application-level relayers. Retrying an HTTP request without an idempotency key may cause the relayer to create another transaction even if the first request merely timed out on the client side.
A timeout says that the caller did not receive an answer. It does not say that the callee did nothing.
While the transaction remains observable by hash, the executor can keep the state as SENT_UNMINED. It should record each observation without rewriting the original submission facts.
Uncertain must be a first-class status
Some failures cannot be settled automatically:
- The process crashed during a remote signing or relay call.
- The provider accepted a request but supplied no durable identifier.
- The account nonce advanced, but the executor cannot identify which transaction used it.
- Different providers disagree about whether a transaction exists.
- A replacement transaction may have been created outside the executor.
Marking these cases failed is unsafe because a retry may duplicate the action. Marking them successful is dishonest because inclusion has not been established.
UNCERTAIN should therefore be terminal for automatic execution. The system may continue collecting evidence, but it must not silently move the record back into the normal retry path. A user or operator can review the evidence and make a new, explicit decision.
The executor also should not invent a compensating transaction. An opposite action is not an undo operation. Prices may have changed, fees have already been spent, and the relevant permission may no longer exist. Compensation is a separate intent requiring separate authorization.
This is less convenient than automatic recovery. It is also more accurate. Distributed systems sometimes lose the evidence needed to distinguish “did not happen” from “happened, but the acknowledgement was lost.” A status model should preserve that fact instead of guessing.
Disclosure: I build Tradevo, software where users describe, test, paper-trade, publish, subscribe to, and automatically run crypto strategies from wallets they control.
The honest executor is not the one that always keeps moving. It is the one that knows when movement would be an unsupported assumption.
Top comments (1)
One more lever when you control the contract: make the action idempotent on-chain. If the call carries the intent ID, or the contract refuses a second action for the same key (a per-intent or per-day guard that reverts), a duplicate send costs a failed transaction instead of a second execution, and UNCERTAIN gets much cheaper to resolve.
The nonce is also stronger evidence than it looks. Once the account nonce has moved past yours and your hash isn't known to any provider, something else used that nonce, which at least tells you your exact signed payload can no longer land.