You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Refactor pkg/relayer from single-token (PROMPT, hardwired) to a mechanism-adapter architecture, with zero behavior change. This is the foundation for USDCx (Circle xReserve) and any future token whose bridge we may or may not operate ourselves.
Design
Full design: docs/bridging-design.md (see epic 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.
typeTokenBridgeinterface {
Key() string// "wayfinder", "xreserve"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
}
typeStepResultstruct {
Status relayer.TransferStatus// pending | in_progress | completed | failedStagestring// mechanism-definedDestTxHash*stringMetadatamap[string]any// merged into transfers.metadataRetryAfter time.Duration
}
Engine becomes two generic loops:
runIngest (one per Source): event -> CreateTransfer (idempotent) -> save offset. Detection only.
runDriver (one for all mechanisms): tick -> load non-terminal transfers with next_step_at <= now -> dispatch Step via registry by t.BridgeKey -> persist StepResult. Subsumes Processor.processEventand the reconcile loop; retry/backoff/stuck-detection written once.
withdrawal: "" -> eth_submitted -> completed; folding CompleteWithdrawal into a stage fixes the existing gap where the retry path skips the post-submit hook
resolve token from the deposit event's token field via config (finally using CantonBridge.sol's existing ethereumToCantonToken mappings)
pkg/app/relayer/server.go — build registry from config (mechanism -> constructor)
Regenerate mocks; existing unit + e2e tests pass unchanged
Acceptance
PROMPT deposits and withdrawals behave identically on devstack (existing e2e green)
Adding a second wayfinder-style token is config-only
pkg/relayer/engine has no imports of pkg/cantonsdk/bridge or pkg/ethereum contract bindings (all mechanism-specific code lives under bridges/wayfinder)
Goal
Refactor
pkg/relayerfrom single-token (PROMPT, hardwired) to a mechanism-adapter architecture, with zero behavior change. This is the foundation for USDCx (Circle xReserve) and any future token whose bridge we may or may not operate ourselves.Design
Full design:
docs/bridging-design.md(see epic for the condensed version).Core abstraction — don't abstract over lock/mint verbs; abstract over the one thing every mechanism shares:
Engine becomes two generic loops:
runIngest(one per Source): event ->CreateTransfer(idempotent) -> save offset. Detection only.runDriver(one for all mechanisms): tick -> load non-terminal transfers withnext_step_at <= now-> dispatchStepvia registry byt.BridgeKey-> persistStepResult. SubsumesProcessor.processEventand the reconcile loop; retry/backoff/stuck-detection written once.Work items
pkg/relayer/bridge.go—TokenBridge,Source(unchanged shape),Registrypkg/relayer/config.go—Tokens map[string]TokenConfig{mechanism, evm_address, decimals, canton{...}}replacing singleeth_token_contract; per-token decimals replaceconst decimalPlaces = 18pkg/relayer/engine/— split intoengine.go(lifecycle),ingest.go,driver.go; deleteprocessor.go+destination.gopkg/relayer/bridges/wayfinder/— port existing logic:source_eth.go+source_canton.go(moved fromengine/source.go),bridge.gowithStep:CreatePendingDeposit->ProcessDepositAndMint-> completed (single step, as today)"" -> eth_submitted -> completed; foldingCompleteWithdrawalinto a stage fixes the existing gap where the retry path skips the post-submit hooktokenfield via config (finally usingCantonBridge.sol's existingethereumToCantonTokenmappings)pkg/migrations/relayerdb/3_multi_token.go—transfers+=bridge_key,token_symbol,stage,metadata jsonb,next_step_at;chain_statekeyed by(bridge_key, chain_id)pkg/relayer/store/—GetSteppableTransfers,ApplyStep,RecordStepErrorpkg/app/relayer/server.go— build registry from config (mechanism -> constructor)Acceptance
pkg/relayer/enginehas no imports ofpkg/cantonsdk/bridgeorpkg/ethereumcontract bindings (all mechanism-specific code lives underbridges/wayfinder)