-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
Frequently asked questions about the StableBridge platform.
StableBridge converts sender fiat (e.g., USD) to USDC stablecoin, transfers it on-chain via Base L2, then converts back to recipient fiat (e.g., EUR). The stablecoin layer is "sandwiched" between two fiat legs, providing fast, low-cost settlement.
US → DE (USD → EUR) using:
- On-ramp: Stripe ACH (fiat collection)
- Chain: Base L2 (USDC transfer)
- Off-ramp: Modulr SEPA (fiat payout)
| Traditional | StableBridge | |
|---|---|---|
| Speed | 2-5 business days | Hours (with instant rails) |
| Cost | $25-50 per transfer | < $5 |
| Transparency | Opaque intermediary chain | On-chain verifiable |
| Settlement | T+2 to T+5 | Near real-time on-chain |
Domain logic must be testable without Spring, JPA, or any infrastructure. The hexagonal pattern ensures domain purity, enables easy testing, and allows swapping infrastructure (e.g., changing from Stripe to another PSP) without touching business logic.
Temporal provides durable execution — workflows survive pod restarts, infrastructure failures, and long-running async operations (e.g., waiting for ACH settlement). It has built-in saga compensation, retry policies, and visibility via the Temporal Web UI. Kafka Streams is better for stateless stream processing, not complex multi-step orchestrations.
Data isolation enables independent deployment, scaling, and technology evolution per service. It prevents tight coupling through shared schemas. Services share data via Kafka events, not direct database access.
Namastack provides a production-ready transactional outbox with retry, batching, and dead-letter handling. It reduces boilerplate compared to a custom implementation and has been validated in production systems.
See the Project Structure page for the tri-module layout. Key steps:
- Create 3 modules:
-api,-client, main service - Apply convention plugins in
build.gradle.kts - Write
ArchitectureTest.javafirst - Create Flyway migration for initial schema
- Implement domain → infrastructure → application layers
Follow the External Provider Adapter Pattern in Design Patterns:
- Create package-private ACL DTOs in
infrastructure/provider/<name>/ - Implement the domain port interface
- Add
@CircuitBreakerannotation - Configure via
@ConfigurationProperties - Write WireMock-based unit tests
- Add
@ConditionalOnPropertyfor provider selection
- Add
static final String TOPICto your domain event record - Create/update the outbox handler to route the event type
- Create consumer in the target service with
@KafkaListener - Ensure consumer is idempotent (check aggregate status before processing)
- Document the topic in Event Driven Architecture
Generic matchers like any() give zero confidence about what was actually called. They mask bugs where the wrong arguments are passed. We require actual values in stubs and eqIgnoringTimestamps/eqIgnoring for verifications. See Testing Standards section 4.
Controllers call domain CommandHandlers directly. An intermediate application service adds unnecessary indirection. The CommandHandler owns all business logic including transaction boundaries. Controllers are thin DTO-mapping shells. See Coding Standards section 3.
| Change Type | Required Tests |
|---|---|
| Domain logic | Unit tests (BDDMockito) |
| New REST endpoint | Unit + Integration (MockMvc) |
| New repository method | Integration (Testcontainers) |
| External provider integration | Unit (WireMock) |
| New Kafka consumer | Integration test |
| End-to-end flow | Business test |
make test-merchant-iam-all # All tiers for one serviceOr via Gradle:
./gradlew :merchant-iam:merchant-iam:test # Unit
./gradlew :merchant-iam:merchant-iam:integrationTest # Integration
./gradlew :merchant-iam:merchant-iam:businessTest # Business- Open Temporal Web UI (
localhost:8233) - Search for workflow ID
payment-{paymentId} - Check which activity/signal the workflow is waiting for
- Check the relevant service logs for errors
- Check if external provider webhook was received
Open Redpanda Console at localhost:9090 and check consumer groups, or use:
kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group <service-group>make infra-destroy # Removes all volumes
make infra-up # Recreates with fresh init.sql- Getting Started — Setup guide
- Troubleshooting — Issue resolution
- Contributing Guide — Development workflow
StableBridge Platform | Source Code | CI/CD | Built with Java 25 + Spring Boot 4 + Temporal + Kafka + Base L2
StableBridge Platform
Architecture & Design
Development
- Getting Started
- Project Structure
- Coding Standards
- Testing Standards
- Database Conventions
- Event Driven Architecture
Operations & Security
Project
Reference