When to use this / when not to use this
Use this when
- You are testing a bridge, message relay, or cross-domain workflow with observable intermediate states.
- You need deterministic local failure injection for restarts, duplicate claims, delayed indexing, or proof readiness.
Do not use this when
- You need economic, validator, or production-network guarantees that a local topology cannot reproduce.
- The protocol exposes no stable identifiers or state probes; add observability before treating timing as correctness.
Write the complete lifecycle before the test
A successful source-chain receipt proves only that the bridge contract accepted an operation. The useful test boundary continues through event observation, service indexing, message or exit-root propagation, proof or claim readiness, destination execution, and the final balance or application state. Sources: Aggkit bridge service flow, Agglayer Unified Bridge asset flow.
Name these states in the CLI and test suite. Protocols differ, but a transition model stops the environment from collapsing every pending condition into waiting. It also tells failure injection where to act: before indexing, after proof creation, during a restart, or immediately before a duplicate claim.
Minimum lifecycle
- Source transaction accepted and expected bridge event emitted.
- Deposit or message indexed with stable origin and destination identifiers.
- Proof, exit root, or claim material becomes available.
- Destination claim accepted exactly once.
- Recipient balance or application state matches the asset and message semantics.
Pin the whole topology, not only contract bytecode
Reproducibility requires chain IDs, genesis state, RPC endpoints, service images, bridge addresses, deployment order, funded accounts, and block timing. Pinning contracts while letting the indexer, relayer, or proof service float still creates failures that another developer cannot reproduce. Sources: Agglayer integration-test topology.
Use one command to start the environment, but keep every component inspectable. Health, chain height, deployed addresses, recent bridge events, pending operations, and claims should be available from the normal CLI surface. A convenient wrapper that hides protocol state makes the first demo easier and every failure harder. Sources: Docker Compose startup and health checks.
Topology manifest
- Network IDs, RPC URLs, ports, block timing, and deterministic accounts.
- Contract artifacts, constructor inputs, deployment transactions, and addresses.
- Service images, configuration digests, dependency order, and health checks.
- Token mappings and native-versus-wrapped asset semantics.
- A reset command that returns every component to the same initial state.
Every arrow is asynchronous and should have its own condition, deadline, and diagnostic source.
Source: AggSandbox bridge operations guide- 01SubmitSource transaction + bridge event
- 02IndexDeposit/message correlation
- 03PropagateExit root, proof, or readiness
- 04ClaimDestination transaction exactly once
- 05AssertFinal asset or application state
Assert each boundary at its source of truth
A final balance can be correct while the test exercised the wrong token mapping, reused a previous claim, or skipped an intermediate service. Read the source receipt and event from the source chain, indexing status from the bridge service, proof readiness from its producer, and destination execution from the destination chain. Sources: Unified Bridge component reference.
Carry one correlation record through the test: source transaction hash, deposit or message identifier, origin and destination network IDs, token and recipient, proof identifier, and claim transaction hash. Each assertion should print the subset needed for the next command when it fails.
High-value cases
- Assets and arbitrary messages in every supported direction.
- Native assets, mapped tokens, and wrapped-token accounting.
- Duplicate claim attempts and idempotent status queries.
- Unsupported network, invalid recipient, and insufficient balance failures.
- Two concurrent deposits with the same asset and recipient.
Turn timeouts and restarts into evidence
A timeout that reports only elapsed seconds throws away the useful state. Poll a named transition with a deadline, store the last observation, and on failure collect source receipt, matching events, service health, indexed message status, destination height, proof readiness, and claim state in one bounded report. Sources: Aggkit claim-readiness polling protocol.
Restart services at deliberate points and verify recovery from durable state. Stop the indexer after source acceptance, restart the claim service after proof readiness, and repeat the status command throughout. Local testing is most valuable when it exercises the recovery paths that are expensive and slow to reproduce on public networks.
Before trusting the suite
- Run from clean state and after an intentional mid-flow restart.
- Break one dependency and confirm the expected transition reports the failure.
- Repeat the suite without inheriting nonces, addresses, or claims from the previous run.
- Keep one end-to-end path fast enough for ordinary development.
- Archive the topology manifest and timeout report in CI artifacts.
A minimal bridge harness that never sleeps blindly
This reduced TypeScript example captures the reusable part of the AggSandbox test strategy: correlate the source transaction, poll one owned state transition, and preserve the last observation when the deadline expires.
type Deposit = { id: string; status: "pending" | "claimable" | "claimed" };
export async function bridgeAndWait(api: BridgeApi, amount: bigint) {
const source = await api.bridgeAsset({ from: 0, to: 1, amount });
const deadline = Date.now() + 60_000;
let last: Deposit | undefined;
while (Date.now() < deadline) {
last = await api.getDeposit(source.depositId);
if (last.status === "claimable") {
return { sourceTx: source.txHash, deposit: last };
}
await api.waitForNextBlock(1);
}
throw new BridgeTimeout({
depositId: source.depositId,
sourceTx: source.txHash,
last
});
}What this minimal boundary guarantees
- The source transaction and deposit identifier remain coupled in the return value.
- Readiness is a protocol state, not elapsed wall-clock time.
- The loop is bounded by a deadline and one-chain block progress.
- Timeouts retain the last bridge-service observation for diagnostics.
Implementation examples
Concrete commands and data shapes you can adapt.
aggsandbox start --detach
aggsandbox bridge asset \
--network-id 0 \
--destination-network-id 1 \
--amount 0.1 \
--token-address 0x0000000000000000000000000000000000000000
aggsandbox show claims --network-id 1await waitFor({
deadlineMs: 60_000,
describe: "deposit becomes claimable",
read: () => bridge.getDeposit(depositId),
accept: (deposit) => deposit.status === "claimable",
onTimeout: (last) => writeStateReport({ depositId, last })
});Decision log
The choices that shape the design—and what each choice costs.
- Use one CLI over a visible Docker topology
Developers get repeatable startup without losing access to individual chain and service state.
Tradeoff: The local environment requires Docker resources and careful port and network-ID management. - Expose intermediate bridge states
A bridge is asynchronous; debugging needs more than source success and final balance.
Tradeoff: The CLI surface must track protocol terminology and service-specific identifiers. - Use condition-based waits
The same tests adapt to fast laptops and slower CI while failing on the missing transition.
Tradeoff: Every polled state needs a bounded API and an actionable timeout report.
Failure cases
What breaks, how it presents, and the recovery boundary.
- Source transaction succeeds but no claim appears
- Signal
The bridge event exists, while the indexing or propagation state never advances.
- Response
Trace the source event identifier through service logs and status APIs before inspecting the destination chain.
- A fixed sleep flakes in CI
- Signal
The same test alternates between passing and missing readiness with no state change in its output.
- Response
Poll the named readiness condition with a deadline and include the last observed state on failure.
- The test passes from stale state
- Signal
A claim or balance from the prior run satisfies the final assertion.
- Response
Use deterministic reset plus per-run identifiers and assert the exact source-to-claim correlation.
Repositories and primary references
Read the implementation, specifications, and tool documentation behind the article.
Related projects
Inspect the systems that ground this guide in implementation work.
Last updated: