Context
Layer 2 provides concrete implementations of each stack interface. Each shim wraps a real service client (go-ethereum, HTTP, database/sql) and satisfies the interface defined in Layer 1. No mocks — shims talk to real services running in Docker.
Files to Create
All files in tests/e2e/devstack/shim/, all with //go:build e2e.
anvil.go
Implements stack.Anvil. Uses go-ethereum/ethclient.
- Constructor:
NewAnvil(manifest *stack.ServiceManifest) (stack.Anvil, error)
- Dials
manifest.AnvilRPC via ethclient.Dial.
- Loads ABIs from embedded Foundry artifact JSON using
//go:embed:
contracts/ethereum-wayfinder/out/PromptToken.sol/PromptToken.json
contracts/ethereum-wayfinder/out/CantonBridge.sol/CantonBridge.json
ERC20Balance: packs balanceOf(owner) calldata, calls client.CallContract, unpacks result.
ApproveAndDeposit:
- Signs and sends
approve(bridgeAddr, amount) on the token contract.
- Signs and sends
deposit(amount, partyID) on the bridge contract.
- Returns the deposit tx hash.
canton.go
Implements stack.Canton. Simple health-check wrapper.
- Constructor:
NewCanton(manifest *stack.ServiceManifest) stack.Canton
IsHealthy: HTTP GET manifest.CantonHTTP + "/v2/version" — returns true on 200.
apiserver.go
Implements stack.APIServer. HTTP client.
- Constructor:
NewAPIServer(manifest *stack.ServiceManifest) stack.APIServer
Register: POST /register with {evm_address, signature, message} JSON body.
GetBalance: JSON-RPC eth_call to /eth — calls balanceOf(owner) on the token contract.
Transfer: JSON-RPC eth_sendTransaction to /eth.
Health: GET /health — returns nil on 200.
relayer.go
Implements stack.Relayer. HTTP client.
- Constructor:
NewRelayer(manifest *stack.ServiceManifest) stack.Relayer
Health: GET /health.
IsReady: GET /ready — returns true on 200.
indexer.go
Implements stack.Indexer. HTTP client with a generic helper.
- Constructor:
NewIndexer(manifest *stack.ServiceManifest) stack.Indexer
- All methods use a shared
getJSON[T](client, url) generic helper that GETs, checks status, and decodes.
- Endpoints follow the pattern
/indexer/v1/admin/... (see docs/E2E_TEST_ARCHITECTURE.md §5 for full URL list).
postgres.go
Implements stack.Postgres. Uses database/sql with lib/pq driver.
- Constructor:
NewPostgres(manifest *stack.ServiceManifest) (stack.Postgres, error)
WhitelistAddress: INSERT INTO whitelist (evm_address) VALUES ($1) ON CONFLICT DO NOTHING
GetUserByEVMAddress: SELECT evm_address, canton_party_id, fingerprint FROM users WHERE evm_address = $1; returns nil, nil for no-rows.
Acceptance Criteria
go build ./tests/e2e/devstack/shim/... compiles cleanly with -tags e2e.
- Each shim struct satisfies its corresponding
stack interface (compiler-enforced via var _ stack.Anvil = (*anvilShim)(nil) assertions).
Dependencies
Notes
- Do not use
testcontainers-go here. Shims connect to services already running in Docker.
- The
go:embed path for ABI files must be relative to the package; use a symlink or path alias if needed.
ApproveAndDeposit should use bind.NewKeyedTransactor pattern consistent with the rest of the codebase.
Size
M (1–2 days)
Context
Layer 2 provides concrete implementations of each stack interface. Each shim wraps a real service client (go-ethereum, HTTP, database/sql) and satisfies the interface defined in Layer 1. No mocks — shims talk to real services running in Docker.
Files to Create
All files in
tests/e2e/devstack/shim/, all with//go:build e2e.anvil.goImplements
stack.Anvil. Usesgo-ethereum/ethclient.NewAnvil(manifest *stack.ServiceManifest) (stack.Anvil, error)manifest.AnvilRPCviaethclient.Dial.//go:embed:contracts/ethereum-wayfinder/out/PromptToken.sol/PromptToken.jsoncontracts/ethereum-wayfinder/out/CantonBridge.sol/CantonBridge.jsonERC20Balance: packsbalanceOf(owner)calldata, callsclient.CallContract, unpacks result.ApproveAndDeposit:approve(bridgeAddr, amount)on the token contract.deposit(amount, partyID)on the bridge contract.canton.goImplements
stack.Canton. Simple health-check wrapper.NewCanton(manifest *stack.ServiceManifest) stack.CantonIsHealthy: HTTP GETmanifest.CantonHTTP + "/v2/version"— returns true on 200.apiserver.goImplements
stack.APIServer. HTTP client.NewAPIServer(manifest *stack.ServiceManifest) stack.APIServerRegister: POST/registerwith{evm_address, signature, message}JSON body.GetBalance: JSON-RPCeth_callto/eth— callsbalanceOf(owner)on the token contract.Transfer: JSON-RPCeth_sendTransactionto/eth.Health: GET/health— returns nil on 200.relayer.goImplements
stack.Relayer. HTTP client.NewRelayer(manifest *stack.ServiceManifest) stack.RelayerHealth: GET/health.IsReady: GET/ready— returns true on 200.indexer.goImplements
stack.Indexer. HTTP client with a generic helper.NewIndexer(manifest *stack.ServiceManifest) stack.IndexergetJSON[T](client, url)generic helper that GETs, checks status, and decodes./indexer/v1/admin/...(seedocs/E2E_TEST_ARCHITECTURE.md§5 for full URL list).postgres.goImplements
stack.Postgres. Usesdatabase/sqlwithlib/pqdriver.NewPostgres(manifest *stack.ServiceManifest) (stack.Postgres, error)WhitelistAddress:INSERT INTO whitelist (evm_address) VALUES ($1) ON CONFLICT DO NOTHINGGetUserByEVMAddress:SELECT evm_address, canton_party_id, fingerprint FROM users WHERE evm_address = $1; returnsnil, nilfor no-rows.Acceptance Criteria
go build ./tests/e2e/devstack/shim/...compiles cleanly with-tags e2e.stackinterface (compiler-enforced viavar _ stack.Anvil = (*anvilShim)(nil)assertions).Dependencies
[E2E] Devstack: Layer 1) — imports interfaces and types.Notes
testcontainers-gohere. Shims connect to services already running in Docker.go:embedpath for ABI files must be relative to the package; use a symlink or path alias if needed.ApproveAndDepositshould usebind.NewKeyedTransactorpattern consistent with the rest of the codebase.Size
M (1–2 days)