Why
Two questions this catalogue has not asked yet. First, the operational one: every agent demo here hand-rolls the dullest and most failure-prone part of on-chain work — nonce management, gas strategy, retries — and OpenZeppelin Relayer is that exact layer, extracted and hardened. Second, and larger: Defender was a managed SaaS that stopped taking sign-ups in June 2025 and shut down on 2026-07-01, handing its functionality to open source on the way out. That makes it a case study in a criterion missing from most infrastructure decisions — not "what does it do" or "what does it cost", but "what remains when the vendor leaves". Defender left well: a year's notice, a migration guide, a production-ready open-source successor. Most vendors will not.
How it works
Relayer keeps the plumbing: it accepts a transaction over a REST API, signs it, and owns nonce sequencing, gas pricing, and retry — EVM multi-chain plus Solana and Stellar, with keys in HashiCorp Vault or AWS KMS rather than an env var. Monitor watches the other direction: declarative JSON rules over events, function calls, and transaction patterns, firing Slack or webhook alerts. The concrete scenario is verex, which already wrote this by hand. Its ChainJob worker executes strictly serially, and its own header explains why: “all txs are sent by the operator or a server-held demo key, so a single lane doubles as nonce management.” Around that sit exponential backoff (5s → 25s → 125s), an atomic PENDING→RUNNING claim, and stuck-job recovery after two minutes — a small relayer, built to make settlement work at all. Adopting the real one deletes the nonce lane, the gas strategy, and the retry ladder, but not onFailed: reversing DB fills after a terminal failure is business logic wearing plumbing's clothes, and no relayer can know that a failed SETTLE_MATCH means two users' balances must be un-credited. The interesting question is the third one. The single lane was serializing business logic as a side effect, not just nonces; widen it and you find out whether that mattered — and this codebase has already produced one bug of exactly that family, a ladder sized from a pre-settlement balanceOf. Monitor addresses the mirror image: that bug was invisible off-chain until it produced a wrong quote, while on-chain it was observable the entire time.
Related code
"""OpenZeppelin Relayer PoC -- nonce-managed transaction queue.
Illustrates the core mechanism: the relayer assigns strictly increasing nonces, retries
failed sends with backoff, and never re-uses a nonce even across retries/failures.
"""
from dataclasses import dataclass, field
@dataclass
class TxRequest:
id: str
payload: str
should_fail_times: int = 0 # simulate transient failures before success
@dataclass
class Relayer:
next_nonce: int = 0
sent: list[tuple[int, str]] = field(default_factory=list) # (nonce, tx id)
def submit(self, req: TxRequest) -> None:
nonce = self.next_nonce
self.next_nonce += 1 # nonce is consumed here, permanently -- never reused
attempts = 0
backoff = [5, 25, 125]
while True:
attempts += 1
ok = attempts > req.should_fail_times
print(f" nonce={nonce} tx={req.id} attempt={attempts} -> {'CONFIRMED' if ok else 'fails, retry'}")
if ok:
self.sent.append((nonce, req.id))
return
if attempts > len(backoff):
print(f" nonce={nonce} tx={req.id} -> exhausted retries, giving up (nonce still not reused)")
self.sent.append((nonce, f"{req.id} (FAILED)"))
return
print(f" backing off {backoff[attempts - 1]}s before retry")
if __name__ == "__main__":
relayer = Relayer()
queue = [
TxRequest("settle-match-1", "transfer(A,B,10)", should_fail_times=0),
TxRequest("settle-match-2", "transfer(C,D,5)", should_fail_times=2),
TxRequest("settle-match-3", "transfer(E,F,7)", should_fail_times=0),
]
print("processing queue, one lane, strictly increasing nonces:")
for req in queue:
relayer.submit(req)
print("\nfinal nonce -> tx mapping (no nonce ever reused):")
for nonce, tx_id in relayer.sent:
print(f" nonce {nonce}: {tx_id}")
← All Dev Notes · Workspace Index · Top ↑ · Open on jaylabs.xyz →