Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.sandbox.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@
# Signup: https://dashboard.stripe.com/register
# Get test key from: Dashboard > Developers > API Keys (toggle "Test mode")
STRIPE_TEST_SECRET_KEY=sk_test_your_key_here
STRIPE_TEST_WEBHOOK_SECRET=whsec_your_webhook_secret_here
STRIPE_SANDBOX_WEBHOOK_SECRET=whsec_your_webhook_secret_here

# --- S4 Blockchain & Custody (Alchemy) ---
# Signup: https://auth.alchemy.com/signup
# Create app for Base Sepolia, copy API key
ALCHEMY_API_KEY=your_alchemy_api_key_here

# --- S4 Blockchain & Custody (Dev EVM Wallet) ---
# Generate a new Sepolia testnet wallet private key (no 0x prefix)
# Fund it via https://www.alchemy.com/faucets/base-sepolia
CUSTODY_DEV_EVM_PRIVATE_KEY=your_evm_private_key_hex_here
Comment on lines +21 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Environment variable mismatch — custody private key won't bind.

CUSTODY_DEV_EVM_PRIVATE_KEY is defined here, but blockchain-custody/application.yml:125 binds to ${DEV_CUSTODY_EVM_PRIVATE_KEY:}. The custody adapter will receive an empty key and fail during sandbox signing.

Align the naming — recommend using the existing DEV_CUSTODY_EVM_PRIVATE_KEY in the template to match the YAML binding.

Proposed fix
 # --- S4 Blockchain & Custody (Dev EVM Wallet) ---
 # Generate a new Sepolia testnet wallet private key (no 0x prefix)
 # Fund it via https://www.alchemy.com/faucets/base-sepolia
-CUSTODY_DEV_EVM_PRIVATE_KEY=your_evm_private_key_hex_here
+DEV_CUSTODY_EVM_PRIVATE_KEY=your_evm_private_key_hex_here
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# --- S4 Blockchain & Custody (Dev EVM Wallet) ---
# Generate a new Sepolia testnet wallet private key (no 0x prefix)
# Fund it via https://www.alchemy.com/faucets/base-sepolia
CUSTODY_DEV_EVM_PRIVATE_KEY=your_evm_private_key_hex_here
# --- S4 Blockchain & Custody (Dev EVM Wallet) ---
# Generate a new Sepolia testnet wallet private key (no 0x prefix)
# Fund it via https://www.alchemy.com/faucets/base-sepolia
DEV_CUSTODY_EVM_PRIVATE_KEY=your_evm_private_key_hex_here
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.sandbox.template around lines 21 - 24, The env var name in the sandbox
template doesn't match the YAML binding: update the variable in
.env.sandbox.template from CUSTODY_DEV_EVM_PRIVATE_KEY to
DEV_CUSTODY_EVM_PRIVATE_KEY so it matches the binding referenced in
blockchain-custody/application.yml (${DEV_CUSTODY_EVM_PRIVATE_KEY:}); ensure the
value is a hex private key (no 0x prefix) and keep the same comment context so
the custody adapter (sandbox signing) receives the expected key.


# --- S4 Blockchain & Custody (Fireblocks — optional) ---
# Request sandbox: https://www.fireblocks.com/developer-sandbox-sign-up/
# Download RSA private key PEM file from Fireblocks console
Expand Down Expand Up @@ -52,6 +57,7 @@ CIRCLE_SANDBOX_DESTINATION_ID=your_wire_destination_id
# Request sandbox: https://www.modulrfinance.com/sandbox
MODULR_SANDBOX_API_KEY=your_modulr_key_here
MODULR_SANDBOX_SOURCE_ACCOUNT_ID=your_source_account_id
MODULR_SANDBOX_API_SECRET=your_modulr_hmac_secret_here

# --- S10 + S13 (JWT Signing Key) ---
# Generate ES256 key pair:
Expand Down
84 changes: 83 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
run-% db-reset db-psql topics \
deps outdated \
assemble sonar fresh ci \
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e \
sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test \
sandbox-tunnel sandbox-env-check
Comment on lines +7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add missing sandbox targets to .PHONY.

sandbox-build, sandbox-destroy, sandbox-logs, and sandbox-test-% are defined later but not declared phony. If matching files exist, make may skip those recipes.

Proposed fix
 .PHONY: help build clean test test-unit test-integration test-business \
        format lint check \
        infra-up infra-down infra-destroy infra-status infra-logs infra-logs-% \
        run-% db-reset db-psql topics \
        deps outdated \
        assemble sonar fresh ci \
        e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e \
-       sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test \
-       sandbox-tunnel sandbox-env-check
+       sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test sandbox-test-% \
+       sandbox-build sandbox-destroy sandbox-logs sandbox-tunnel sandbox-env-check
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e \
sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test \
sandbox-tunnel sandbox-env-check
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e \
sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test sandbox-test-% \
sandbox-build sandbox-destroy sandbox-logs sandbox-tunnel sandbox-env-check
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 7 - 9, The .PHONY list is missing several sandbox
targets so make might skip their recipes when files with those names exist;
update the .PHONY declaration to include the sandbox targets: add sandbox-build,
sandbox-destroy, sandbox-logs, and the pattern sandbox-test-% (in addition to
existing sandbox-run-%) so the targets defined later (sandbox-build,
sandbox-destroy, sandbox-logs, sandbox-test-%) are always executed as phony
targets.


# ─────────────────────────────────────────────
# Variables
Expand Down Expand Up @@ -170,3 +172,83 @@ e2e-test: ## Run Phase 3 E2E tests (stack must be running)
PHASE3_TESTS_ENABLED=true $(GRADLE) :phase3-integration-tests:test --rerun

e2e: e2e-build e2e-up e2e-test ## Build images, start stack, run E2E tests

# ─────────────────────────────────────────────
# Sandbox Testing (real external APIs)
# ─────────────────────────────────────────────
# Requires: .env.sandbox with real API keys (copy from .env.sandbox.template)
# Infra: Docker Compose for Postgres, Kafka, Redis, Temporal
# Tunnel: cloudflared for Stripe webhooks

SANDBOX_SERVICES := compliance-travel-rule fx-liquidity-engine fiat-on-ramp \
blockchain-custody fiat-off-ramp ledger-accounting payment-orchestrator

sandbox-env-check: ## Verify .env.sandbox exists and key vars are set
@test -f .env.sandbox || (echo "ERROR: .env.sandbox not found. Run: cp .env.sandbox.template .env.sandbox" && exit 1)
@. ./.env.sandbox && test -n "$$STRIPE_TEST_SECRET_KEY" || (echo "ERROR: STRIPE_TEST_SECRET_KEY not set in .env.sandbox" && exit 1)
@. ./.env.sandbox && test -n "$$ALCHEMY_API_KEY" || (echo "ERROR: ALCHEMY_API_KEY not set in .env.sandbox" && exit 1)
@echo "✓ .env.sandbox loaded — keys present"
Comment on lines +186 to +190

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Expand sandbox-env-check to validate all required sandbox secrets/IDs.

Current checks only cover Stripe API key and Alchemy key. The compose stack and sandbox tests also depend on additional required vars (e.g., Circle destination, Modulr credentials/secrets, Persona keys, Stripe webhook secret, custody private key). Missing values will fail late at runtime.

Proposed fix
 sandbox-env-check: ## Verify .env.sandbox exists and key vars are set
 	`@test` -f .env.sandbox || (echo "ERROR: .env.sandbox not found. Run: cp .env.sandbox.template .env.sandbox" && exit 1)
-	@. ./.env.sandbox && test -n "$$STRIPE_TEST_SECRET_KEY" || (echo "ERROR: STRIPE_TEST_SECRET_KEY not set in .env.sandbox" && exit 1)
-	@. ./.env.sandbox && test -n "$$ALCHEMY_API_KEY" || (echo "ERROR: ALCHEMY_API_KEY not set in .env.sandbox" && exit 1)
+	`@set` -a; . ./.env.sandbox; set +a; \
+	required="STRIPE_TEST_SECRET_KEY STRIPE_SANDBOX_WEBHOOK_SECRET ALCHEMY_API_KEY CUSTODY_DEV_EVM_PRIVATE_KEY PERSONA_SANDBOX_API_KEY PERSONA_INQUIRY_TEMPLATE_ID CIRCLE_SANDBOX_API_KEY CIRCLE_SANDBOX_DESTINATION_ID MODULR_SANDBOX_API_KEY MODULR_SANDBOX_SOURCE_ACCOUNT_ID MODULR_SANDBOX_API_SECRET"; \
+	for k in $$required; do eval "v=\$${$$k}"; [ -n "$$v" ] || { echo "ERROR: $$k not set in .env.sandbox"; exit 1; }; done
 	`@echo` "✓ .env.sandbox loaded — keys present"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 186 - 190, Update the sandbox-env-check Makefile
target to validate all required sandbox env vars by adding the same pattern used
for STRIPE_TEST_SECRET_KEY and ALCHEMY_API_KEY for each missing secret/ID: e.g.,
add checks for CIRCLE_DESTINATION, MODULR_CLIENT_ID, MODULR_CLIENT_SECRET,
PERSONA_API_KEY, STRIPE_WEBHOOK_SECRET, CUSTODY_PRIVATE_KEY (or whatever exact
names are used in the codebase) using ". ./.env.sandbox && test -n
\"$$VAR_NAME\" || (echo \"ERROR: VAR_NAME not set in .env.sandbox\" && exit 1)";
keep the same overall structure and final success echo in the sandbox-env-check
target.


SANDBOX_COMPOSE := docker compose --env-file .env.sandbox -f docker-compose.sandbox.yml

sandbox-up: sandbox-env-check sandbox-build ## Build images, start infra + all 7 services with real sandbox APIs
$(SANDBOX_COMPOSE) up -d
@echo ""
@echo "✓ Sandbox stack launching (7 services + infra)"
@echo " Run: make sandbox-status to check health"
@echo " Run: make sandbox-logs to tail all logs"
@echo " Run: make sandbox-test to run adapter tests"

sandbox-down: ## Stop all sandbox containers
$(SANDBOX_COMPOSE) down
@echo "✓ Sandbox stopped"

sandbox-destroy: ## Stop sandbox and remove volumes (full reset)
$(SANDBOX_COMPOSE) down -v
@echo "✓ Sandbox destroyed"

sandbox-build: ## Build all service Docker images for sandbox
$(GRADLE) $(foreach s,$(SERVICES),:$(s):$(s):jibDockerBuild) --parallel
@echo "✓ All Docker images built"

sandbox-logs: ## Tail all sandbox service logs
$(SANDBOX_COMPOSE) logs -f --tail=50

sandbox-tunnel: ## Start cloudflared tunnel for Stripe webhooks (runs in foreground)
@echo "Starting Cloudflare tunnel → localhost:8085 (S3 Fiat On-Ramp)"
@echo "Copy the https://xxx.trycloudflare.com URL to Stripe webhook dashboard"
@echo "Endpoint path: /on-ramp/internal/webhooks/psp/stripe"
@echo ""
cloudflared tunnel --url http://localhost:8085

sandbox-run-%: sandbox-env-check ## Run a service in sandbox mode (e.g., make sandbox-run-fiat-on-ramp)
set -a && . ./.env.sandbox && set +a && \
$(GRADLE) :$*:$*:bootRun --args='--spring.profiles.active=sandbox'

sandbox-test: sandbox-env-check ## Run all sandbox adapter tests against real APIs
set -a && . ./.env.sandbox && set +a && \
$(GRADLE) \
:fiat-on-ramp:fiat-on-ramp:test --tests '*StripeAdapterSandboxTest*' \
:blockchain-custody:blockchain-custody:test --tests '*EvmRpcAdapterSandboxTest*' --tests '*SolanaRpcAdapterSandboxTest*' --tests '*FireblocksCustodyAdapterSandboxTest*' \
:fiat-off-ramp:fiat-off-ramp:test --tests '*CircleRedemptionAdapterSandboxTest*' --tests '*ModulrPayoutAdapterSandboxTest*' \
:fx-liquidity-engine:fx-liquidity-engine:test --tests '*FrankfurterRateAdapterSandboxTest*' \
:merchant-onboarding:merchant-onboarding:test --tests '*CompaniesHouseAdapterSandboxTest*'

sandbox-test-%: sandbox-env-check ## Run sandbox tests for a service (e.g., make sandbox-test-fiat-on-ramp)
set -a && . ./.env.sandbox && set +a && \
$(GRADLE) :$*:$*:test --tests '*SandboxTest*'

sandbox-status: ## Show infra status + sandbox env summary
@$(SANDBOX_COMPOSE) ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || $(SANDBOX_COMPOSE) ps
@echo ""
Comment thread
coderabbitai[bot] marked this conversation as resolved.
@echo "--- Sandbox API Keys ---"
@test -f .env.sandbox && . ./.env.sandbox && \
echo "Stripe: $${STRIPE_TEST_SECRET_KEY:+set}" && \
echo "Alchemy: $${ALCHEMY_API_KEY:+set}" && \
echo "Persona: $${PERSONA_SANDBOX_API_KEY:+set}" && \
echo "Companies House: $${COMPANIES_HOUSE_API_KEY:+set}" && \
echo "Circle: $${CIRCLE_SANDBOX_API_KEY:+set}" && \
echo "Modulr: $${MODULR_SANDBOX_API_KEY:+set}" && \
echo "Fireblocks: $${FIREBLOCKS_SANDBOX_API_KEY:+set}" && \
echo "JWT Key: $${JWT_PRIVATE_KEY_BASE64:+set}" \
|| echo "No .env.sandbox found"
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ api-gateway-iam:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/s10_api_gateway_iam
username: sp_user
password: sp_pass
username: dev
password: dev

cloud:
vault:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public ApiError handleWalletNotFound(WalletNotFoundException ex) {
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ExceptionHandler(CustodySigningException.class)
public ApiError handleCustodySigning(CustodySigningException ex) {
log.error("Custody signing error: {}", ex.getClass().getSimpleName());
log.error("Custody signing error: {}", ex.getMessage());
return ApiError.of(CustodySigningException.ERROR_CODE,
INTERNAL_SERVER_ERROR.getReasonPhrase(), "Custody signing failed");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,13 @@ SignResult signAndSubmitEvm(SignRequest request) {
var credentials = Credentials.create(properties.evmPrivateKey());

var usdcContract = chainConfig.usdcContract();
var amountMinorUnits = request.amount()
.movePointRight(USDC_DECIMALS)
.toBigIntegerExact();
var scaledAmount = request.amount().movePointRight(USDC_DECIMALS).stripTrailingZeros();
if (scaledAmount.scale() > 0) {
throw new DevCustodyException(
"Amount has sub-minor-unit precision after scaling to %d decimals: %s"
.formatted(USDC_DECIMALS, request.amount()));
}
var amountMinorUnits = scaledAmount.toBigInteger();
var data = encodeErc20Transfer(request.toAddress(), amountMinorUnits);

var nonce = request.nonce() != null ? BigInteger.valueOf(request.nonce()) : BigInteger.ZERO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/s4_blockchain_custody
username: sp_user
password: sp_pass
username: dev
password: dev

cloud:
vault:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ app:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/s2_compliance
username: sp_user
password: sp_pass
username: dev
password: dev

cloud:
vault:
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,12 @@ services:
ports:
- "7233:7233"
environment:
DB: postgresql
DB: postgres12
DB_PORT: 5432
POSTGRES_USER: dev
POSTGRES_PWD: dev
POSTGRES_SEEDS: postgres
DYNAMIC_CONFIG_FILE_PATH: /etc/temporal/config/dynamicconfig/development-sql.yaml
SKIP_DYNAMIC_CONFIG_SETUP: true
depends_on:
postgres:
condition: service_healthy
Expand Down Expand Up @@ -241,7 +241,7 @@ services:
# Jaeger — distributed trace visualization (STA-221)
# ─────────────────────────────────────────────
jaeger:
image: jaegertracing/all-in-one:1.67
image: jaegertracing/all-in-one:1.76.0
container_name: sp-jaeger
ports:
- "16686:16686" # Jaeger UI
Expand Down
Loading
Loading