Executor Protocol
What is an Executor?
An Executor is, in one word, an “actor.” It is the component that actually moves funds, checks status, and recovers when something goes wrong. Think of it as an international courier system:
The key point is that all of these Executors follow a single common interface. Whether it is the courier driver or the customs officer, they all file the same report form — which lets the system manage them in a uniform way.
The executor contract is six methods:
submit and recover can take a DB session that the dispatcher threads in. This lets an executor that needs to read or write the DB (e.g. nonce reservation, intent re-derivation) participate in the orchestrator’s transaction instead of opening its own. Executors that don’t need it simply ignore it.The 6 Methods of the Executor Contract
Here is the full execution lifecycle shown as a single sequence:Each method in detail
1. preflight — “Can you do this?”
Pre-execution validation. A return value of None means pass; returning an ExecutionResult means a problem was found.
For a CEX withdrawal, for example:
- Is the withdrawal address on the whitelist?
- Is withdrawal currently available? (not under maintenance)
- Is the balance sufficient?
- Is the amount within the min/max limits?
2. prepare — “Get ready”
Packages what is going to be done into a prepared-action object. Nothing is executed yet.
For CEX: serializes a payload like { exchange: "upbit", asset: "XRP", amount: "100", address: "..." }.For CCTP: constructs the EVM transaction (calldata, nonce, gas) for the burn call.
3. submit — “Execute!”
Actually executes the prepared action. For CEX, it calls the withdrawal API; for on-chain, it broadcasts the signed transaction.
The signed payload arrives inside the prepared action (the orchestrator runs the signer between prepare and submit). A submit that needs the DB — for example re-deriving the signed recipient or reserving an EVM nonce — can run inside the orchestrator’s transaction via the session the dispatcher threads in.
4. observe — “Check the result”
Checks the status of an asynchronous operation. Returns OBSERVING if still in progress, COMPLETED when done, and FAILED on failure.
Why observe is a separate method
A blockchain TX or exchange withdrawal does not finish the moment you submit it. Withdrawals take minutes to hours, and on-chain TXs require confirmations. That is why “execution” and “confirmation” are separate.
5. recover — “Something seems off — try to recover”
Called when the system is in an UNKNOWN state. Example: the withdrawal API was called but timed out. Funds may or may not have gone out. recover investigates and classifies the situation as COMPLETED or FAILED. Like submit, it can run reconciliation inside the orchestrator’s transaction via the DB session the dispatcher threads in.
6. health — “Are you alive?”
Returns a small status dict (or nothing) describing whether the executor’s downstream dependency is reachable. It is polled at bootstrap and by the executor-health worker, and feeds the approval-time availability gate described below. Unlike the other five methods, health takes no execution context — it is a standalone liveness probe.
Execution Context — All the information needed to execute
Think of this as the “work order” handed to an Executor. It contains everything needed to carry out the task. Key fields grouped by role:Local vs Remote Executor
Registration decides Local vs Remote — not the key. Whichever path wins is decided when the executor is registered: a Local executor wraps an in-process handler that runs directly, or a Remote executor proxies to an HTTP service. The registry is a flat lookup keyed by the exact executor key; resolving a binding is a single exact-key lookup with no prefix parsing.A non-Python lane such as Solana is illustrative — it sketches how a non-Python transport would plug in over the Remote path. The Remote path exists today, but no Solana executor ships; the live executors are the CEX, CCTP/CCIP/Gateway/USDT0/EVM, and Hyperliquid lanes.
Local Executor
Executes directly in-process. It delegates each lifecycle call to the underlying execution logic.Why separate the wrapper from the logic
The system interface concerns (carrying the executor key, registry registration, health checks) are kept apart from the actual execution logic. The execution logic stays pure business logic, while the wrapper handles the system concerns.
Remote Executor
Proxies each lifecycle call to an external service over HTTP JSON. Each method maps to an HTTP endpoint (/preflight, /submit, /observe, …); a no-content response on preflight means “passed,” otherwise the JSON body is the execution result.
Executor Registry
All Executors are registered in a central registry under an exact key, and a binding is resolved by exact-key lookup. The key format follows theexec.{lane}.{role} pattern:
exec.cex.withdrawal_actionexec.cex.withdrawal_observeexec.cex.deposit_observeexec.cctp.burnexec.cctp.mint
Prepared Action and Signing
The prepared action thatprepare produces is a “specification of what will be done” — it is an execution plan, not the execution itself.
A prepared action carries, conceptually:
For CEX withdrawals, signing is not required — exchange API key authentication is handled inside the adapter. For CCTP, signing is required — blockchain transactions require a private key signature.
Health State and Binding Resolution Policy
3-layer defense model
An Executor/Signer’s health state can be checked at 3 points:Why not filter at Resolution?
- Health is transient. An executor that is down at compile time may be up by dispatch time
- Resolution expresses intent. A plan’s binding decides “which executor to use,” not “is it alive right now”
- The Approval-time gate is sufficient. The availability gate checks health just before approval, preventing execution from starting with a down executor
- Filtering at Resolution degrades UX. A transient down state causing a resolution error would force the operator to recreate the movement
What if health changes after approval?
If an executor goes down after approval:- The Dispatcher fails at the executor’s
preflightorsubmit - The Node transitions to
FAILEDstate - The Recovery worker attempts recovery