ChainSafe / ChainSafe/canton-middleware
feat(relayer): TokenBridge adapter foundation (interface, registry, driver)
- Dominant language
- Go
- Stars
- 1
- Forks
- 1
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
# Goal
Introduce the multi-token `TokenBridge` adapter foundation in `pkg/relayer` — interface, registry, driver loop, schema, per-token config. **Scope decision (2026-07-11): the existing PROMPT pipeline is NOT ported in this issue.** It keeps running unchanged (processor + reconcile loop as today); the new driver runs alongside it and serves adapter-registered tokens only (first consumer: xreserve/USDCx, #357). Porting PROMPT to a `wayfinder` adapter is deferred to a follow-up issue.
# Design
Full design: `docs/bridging-design.md` (see epic #361 for the condensed version).
Core abstraction — don't abstract over lock/mint verbs; abstract over the one thing every mechanism shares:
> A transfer is a durable record advanced by an idempotent step function until terminal.
```go
type TokenBridge interface {
Key() string // "xreserve", later "wayfinder"
Sources(ctx context.Context) ([]Source, error) // event streams (empty for observer mechanisms)
Step(ctx context.Context, t *relayer.Transfer) (StepResult, error) // advance one stage, never block
}
type StepResult struct {
Status relayer.TransferStatus // pending | in_progress | completed | failed
Stage string // mechanism-defined
DestTxHash *string
Metadata map[string]any // merged into transfers.metadata
RetryAfter time.Duration
}
```
Engine additions (new code, not a rewrite of the existing pipeline):
- `runIngest` (one per Source): event -> `CreateTransfer` (idempotent) -> save offset. Detection only. Exercised only by tests until a sourced (executor) adapter registers.
- `runDriver` (one for all mechanisms): tick -> load non-terminal adapter transfers with `next_step_at <= now` -> dispatch `Step` via registry by `t.BridgeKey` -> persist `StepResult`. Retry/backoff/stuck-detection written once.
# Work items
- [ ] `pkg/relayer/bridge.go` — `TokenBridge`, `Source` (same shape as today's), `StepResult`, `Registry`
- [ ] `pkg/relayer/config.go` — add `Tokens map[string]TokenConfig{mechanism, evm_address, decimals, ...}`; existing single-token fields stay for the legacy PROMPT pipeline
- [ ] `pkg/relayer/engine/` — add `driver.go` + `ingest.go`; **do not** delete/modify `processor.go`, `destination.go`, `source.go` (they serve PROMPT until the port)
- [ ] `pkg/migrations/relayerdb/3_multi_token.go` — `transfers` += `bridge_key` (backfill existing rows to `wayfinder`), `token_symbol`, `stage`, `metadata jsonb`, `next_step_at`; new columns nullable/defaulted so the legacy write path is untouched. `chain_state` composite key `(bridge_key, chain_id)` with existing rows keyed to `wayfinder`
- [ ] `pkg/relayer/store/` — `GetSteppableTransfers` (filters to registry-known bridge keys), `ApplyStep`, `RecordStepError`
- [ ] `pkg/app/relayer/server.go` — build registry from config (mechanism -> constructor); legacy pipeline wiring unchanged
- [ ] Mocks for `TokenBridge`/`Source`; driver unit tests with a fake adapter (stage progression, backoff, RetryAfter, unknown-key handling)
# Acceptance
- PROMPT deposits and withdrawals behave identically on devstack (existing e2e green, legacy pipeline untouched)
- A fake adapter registered in config gets driven through its stages by `runDriver`
- Driver ignores legacy `wayfinder`-keyed rows (owned by the old pipeline until the port)
# Follow-up (deferred)
- Port PROMPT to `bridges/wayfinder/` and delete the legacy pipeline — separate issue.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with docs/bridging-design.md, then read pkg/relayer/bridge.go and the existing engine, store, migration, and server entry points named in the work items. Add the fake-adapter driver tests for stage progression, backoff, RetryAfter, and unknown keys, and run the existing devstack e2e checks. Done means the configured fake adapter advances while legacy PROMPT behavior and wayfinder-keyed rows remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100