QA: Verify Solana integration — full operator, frontend, canceler, and faucet wiring #67

Closed
opened 2026-03-23 05:33:04 +00:00 by PlasticDigits · 26 comments
PlasticDigits commented 2026-03-23 05:33:04 +00:00 (Migrated from gitlab.com)

Summary

Commit f6e0704 on feat/solana-integration wires the full Solana integration across all services. This needs QA verification before merging.

What changed

Operator (Rust)

  • SolanaConfig added to Config — loaded from SOLANA_RPC_URL, SOLANA_PROGRAM_ID, SOLANA_PRIVATE_KEY env vars
  • SolanaWatcher now constructed and started when config is present
  • SolanaWriter spawned as a standalone task with shutdown handling

Frontend (React/TypeScript)

  • Solana deposits enabled in TransferForm (blocking guards removed, useSolanaDeposit wired)
  • fetchDepositNonce, computeTransferHash, buildWithdrawSubmitInstruction added to transaction service
  • submitOnSolana added to useWithdrawSubmit
  • useAutoWithdrawSubmit updated with Solana branches (blockReason, triggerSubmit, startPolling)
  • Route validation fixed for Solana destinations (solanaTokenExists instead of falling through to Terra)
  • TransferStatusPage updated with Solana direction handling
  • BridgeChainConfig type extended with programId, explorerTxUrl, faucetAddress

Faucet

  • New cl8y-faucet Anchor program: initialize, register_mint, claim (SPL with cooldown), claim_sol
  • setup-test-tokens.sh script for creating test SPL mints
  • FaucetPanel updated with SolanaBalanceCell, SolanaClaimButton, SolAirdropButton

Canceler (Rust)

  • read_pending_withdraw_full parses all PendingWithdraw PDA fields
  • poll_solana_approvals no longer uses zeroed-placeholder data
  • SolanaCancelerClient accepts configurable commitment level

QA checklist

  • Operator starts with SOLANA_RPC_URL set and logs show Solana watcher + writer initialized
  • Operator starts without SOLANA_RPC_URL and Solana modules are skipped cleanly
  • Frontend: Solana → EVM deposit flow works end-to-end (test validator)
  • Frontend: EVM → Solana withdrawal (withdraw_submit) completes
  • Frontend: Route validation rejects invalid Solana destination tokens
  • Frontend: TransferStatusPage shows correct direction for Solana transfers
  • FaucetPanel: SOL airdrop button works on local validator
  • FaucetPanel: SPL token claim button works after faucet program is deployed
  • Canceler: poll_solana_approvals reads full PDA data (check logs for populated fields)
  • Canceler: Commitment config is respected (set SOLANA_COMMITMENT=confirmed and verify)
  • cl8y-faucet program builds with anchor build
  • setup-test-tokens.sh creates mints successfully

/cc @Brouie

## Summary Commit `f6e0704` on `feat/solana-integration` wires the full Solana integration across all services. This needs QA verification before merging. ### What changed **Operator (Rust)** - `SolanaConfig` added to `Config` — loaded from `SOLANA_RPC_URL`, `SOLANA_PROGRAM_ID`, `SOLANA_PRIVATE_KEY` env vars - `SolanaWatcher` now constructed and started when config is present - `SolanaWriter` spawned as a standalone task with shutdown handling **Frontend (React/TypeScript)** - Solana deposits enabled in `TransferForm` (blocking guards removed, `useSolanaDeposit` wired) - `fetchDepositNonce`, `computeTransferHash`, `buildWithdrawSubmitInstruction` added to transaction service - `submitOnSolana` added to `useWithdrawSubmit` - `useAutoWithdrawSubmit` updated with Solana branches (blockReason, triggerSubmit, startPolling) - Route validation fixed for Solana destinations (`solanaTokenExists` instead of falling through to Terra) - `TransferStatusPage` updated with Solana direction handling - `BridgeChainConfig` type extended with `programId`, `explorerTxUrl`, `faucetAddress` **Faucet** - New `cl8y-faucet` Anchor program: `initialize`, `register_mint`, `claim` (SPL with cooldown), `claim_sol` - `setup-test-tokens.sh` script for creating test SPL mints - `FaucetPanel` updated with `SolanaBalanceCell`, `SolanaClaimButton`, `SolAirdropButton` **Canceler (Rust)** - `read_pending_withdraw_full` parses all PendingWithdraw PDA fields - `poll_solana_approvals` no longer uses zeroed-placeholder data - `SolanaCancelerClient` accepts configurable commitment level ### QA checklist - [ ] Operator starts with `SOLANA_RPC_URL` set and logs show Solana watcher + writer initialized - [ ] Operator starts without `SOLANA_RPC_URL` and Solana modules are skipped cleanly - [ ] Frontend: Solana → EVM deposit flow works end-to-end (test validator) - [ ] Frontend: EVM → Solana withdrawal (withdraw_submit) completes - [ ] Frontend: Route validation rejects invalid Solana destination tokens - [ ] Frontend: TransferStatusPage shows correct direction for Solana transfers - [ ] FaucetPanel: SOL airdrop button works on local validator - [ ] FaucetPanel: SPL token claim button works after faucet program is deployed - [ ] Canceler: `poll_solana_approvals` reads full PDA data (check logs for populated fields) - [ ] Canceler: Commitment config is respected (set `SOLANA_COMMITMENT=confirmed` and verify) - [ ] `cl8y-faucet` program builds with `anchor build` - [ ] `setup-test-tokens.sh` creates mints successfully /cc @Brouie
PlasticDigits commented 2026-03-23 05:33:05 +00:00 (Migrated from gitlab.com)

assigned to @Brouie

assigned to @Brouie
Brouie commented 2026-03-23 05:53:47 +00:00 (Migrated from gitlab.com)

Initial #67 findings: (1) anchor-cli on server is 1.0.0-rc.4 but project targets 0.32.1. Causes key mismatch on build. avm not installed so cannot downgrade without dev. (2) cl8y-faucet fails to compile (23 errors: E0277, E0432, E0599). Likely anchor version mismatch. (3) register-tokens.sh on devnet still blocked — same root cause as #62 (fresh keypairs every run). (4) anchor keys sync worked but anchor build -p cl8y_bridge then shows different keypair hash (EhetQnc vs CNWdzCX). Suspect rc.4 regenerates deploy keys. (5) Bridge .so from previous build still works — 35/35 tests pass on localnet including test fix 9b4f6b9. Pivoting to DEX #18 while devnet blockers are addressed.

Initial #67 findings: (1) anchor-cli on server is 1.0.0-rc.4 but project targets 0.32.1. Causes key mismatch on build. avm not installed so cannot downgrade without dev. (2) cl8y-faucet fails to compile (23 errors: E0277, E0432, E0599). Likely anchor version mismatch. (3) register-tokens.sh on devnet still blocked — same root cause as #62 (fresh keypairs every run). (4) anchor keys sync worked but anchor build -p cl8y_bridge then shows different keypair hash (EhetQnc vs CNWdzCX). Suspect rc.4 regenerates deploy keys. (5) Bridge .so from previous build still works — 35/35 tests pass on localnet including test fix 9b4f6b9. Pivoting to DEX #18 while devnet blockers are addressed.
PlasticDigits commented 2026-03-23 06:17:38 +00:00 (Migrated from gitlab.com)

mentioned in commit 368e8ba99c

mentioned in commit 368e8ba99c8332b4aa1ec22e067a9d12c276c42c
PlasticDigits commented 2026-03-23 06:17:49 +00:00 (Migrated from gitlab.com)

@Brouie Fix pushed to feat/solana-integration (368e8ba). The compile errors were not just CLI version skew — cl8y-faucet/Cargo.toml was missing the init-if-needed feature on anchor-lang and the associated_token feature on anchor-spl, which its account constraints require. Both are now enabled, plus idl-build for IDL generation parity.

Also pinned anchor_version = "0.32.1" and solana_version = "2.2.12" in Anchor.toml so the CLI matches the crates (should fix the deploy-key regeneration you saw). Added faucet integration tests (faucet.test.ts) and a CI job for contracts-solana. Please re-test anchor build -p cl8y_faucet on the server.

@Brouie Fix pushed to `feat/solana-integration` (368e8ba). The compile errors were not just CLI version skew — `cl8y-faucet/Cargo.toml` was missing the `init-if-needed` feature on `anchor-lang` and the `associated_token` feature on `anchor-spl`, which its account constraints require. Both are now enabled, plus `idl-build` for IDL generation parity. Also pinned `anchor_version = "0.32.1"` and `solana_version = "2.2.12"` in `Anchor.toml` so the CLI matches the crates (should fix the deploy-key regeneration you saw). Added faucet integration tests (`faucet.test.ts`) and a CI job for `contracts-solana`. Please re-test `anchor build -p cl8y_faucet` on the server.
Brouie commented 2026-03-23 07:20:34 +00:00 (Migrated from gitlab.com)

Pulled 368e8ba. Faucet build still blocked — anchor-cli 1.0.0-rc.4 regenerates deploy keypairs on build even after keys sync. keys sync sets GDRbn9 but build sees 9P336P. avm install fails too (not properly installed). Can you install anchor-cli 0.32.1 on the server? Can't do any builds until that's resolved.

Pulled 368e8ba. Faucet build still blocked — anchor-cli 1.0.0-rc.4 regenerates deploy keypairs on build even after keys sync. keys sync sets GDRbn9 but build sees 9P336P. avm install fails too (not properly installed). Can you install anchor-cli 0.32.1 on the server? Can't do any builds until that's resolved.
PlasticDigits commented 2026-03-23 08:52:24 +00:00 (Migrated from gitlab.com)

@Brouie Yes, added 0.32.1, please check

@Brouie Yes, added 0.32.1, please check
PlasticDigits commented 2026-03-23 09:53:30 +00:00 (Migrated from gitlab.com)

@Brouie When you have a moment, please review the latest Solana QA fixes on feat/solana-integration: faucet claim_sol now moves lamports from the config PDA correctly (no SystemProgram CPI from a data-carrying account), Anchor/npm tooling is standardized (package-lock, .npmrc, removed yarn.lock), and anchor test should be green including faucet. QA server: pull as your user, npm ci in packages/contracts-solana + frontend if needed, then anchor build / anchor test --skip-build. Thanks.

@Brouie When you have a moment, please review the latest Solana QA fixes on `feat/solana-integration`: faucet `claim_sol` now moves lamports from the config PDA correctly (no SystemProgram CPI from a data-carrying account), Anchor/npm tooling is standardized (`package-lock`, `.npmrc`, removed `yarn.lock`), and `anchor test` should be green including faucet. QA server: pull as your user, `npm ci` in `packages/contracts-solana` + frontend if needed, then `anchor build` / `anchor test --skip-build`. Thanks.
Brouie commented 2026-03-24 08:24:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #74

mentioned in issue #74
Brouie commented 2026-03-25 03:27:50 +00:00 (Migrated from gitlab.com)

Final #67 verification status:

[x] Operator starts with SOLANA_RPC_URL — Solana watcher + writer initialized (verified 3/24)
[x] Operator starts without SOLANA_RPC_URL — Solana modules skipped cleanly (verified 3/24)
[x] cl8y-faucet builds with anchor build (verified 3/24)
[x] Frontend: Solana chain appears in chain list — PASS
[x] Frontend: Solana wallet connection (Phantom) — PASS
[x] Frontend: Recipient autofill with connected wallet — PASS
[x] Frontend: Solana RPC health check in Settings — green 102ms (fixed in #74, closed)
[x] Frontend: Route validation for Solana destinations — PASS
[ ] Frontend: Solana deposit execution — blocked by CSP WebSocket (#75)
[ ] Frontend: Solana withdraw execution — blocked by #75
[ ] FaucetPanel: SOL airdrop / SPL claim — no faucet configured in .env.local
[ ] Canceler: poll_solana_approvals — not tested (needs operator + canceler running together)
[ ] setup-test-tokens.sh — not tested
[ ] Cross-chain E2E — 4/6 pass, #70 open (operator keypair mismatch)

8/13 verified. Remaining 5 blocked by #70 and #75.

Final #67 verification status: [x] Operator starts with SOLANA_RPC_URL — Solana watcher + writer initialized (verified 3/24) [x] Operator starts without SOLANA_RPC_URL — Solana modules skipped cleanly (verified 3/24) [x] cl8y-faucet builds with anchor build (verified 3/24) [x] Frontend: Solana chain appears in chain list — PASS [x] Frontend: Solana wallet connection (Phantom) — PASS [x] Frontend: Recipient autofill with connected wallet — PASS [x] Frontend: Solana RPC health check in Settings — green 102ms (fixed in #74, closed) [x] Frontend: Route validation for Solana destinations — PASS [ ] Frontend: Solana deposit execution — blocked by CSP WebSocket (#75) [ ] Frontend: Solana withdraw execution — blocked by #75 [ ] FaucetPanel: SOL airdrop / SPL claim — no faucet configured in .env.local [ ] Canceler: poll_solana_approvals — not tested (needs operator + canceler running together) [ ] setup-test-tokens.sh — not tested [ ] Cross-chain E2E — 4/6 pass, #70 open (operator keypair mismatch) 8/13 verified. Remaining 5 blocked by #70 and #75.
Brouie commented 2026-03-25 08:44:10 +00:00 (Migrated from gitlab.com)

E2E update: 6/6 Solana E2E tests ALL PASS (make solana-test-e2e with --test-threads=1). Verified flows: evm_to_solana, solana_to_evm_deposit, cancel_flow, bridge_program_exists, admin_funded, validator_running. #70 closed. Remaining #67 items: frontend Solana deposit/withdraw flows, FaucetPanel.

E2E update: 6/6 Solana E2E tests ALL PASS (make solana-test-e2e with --test-threads=1). Verified flows: evm_to_solana, solana_to_evm_deposit, cancel_flow, bridge_program_exists, admin_funded, validator_running. #70 closed. Remaining #67 items: frontend Solana deposit/withdraw flows, FaucetPanel.
PlasticDigits commented 2026-03-25 09:20:10 +00:00 (Migrated from gitlab.com)

Frontend #67: Solana deposit_native + withdraw_submit tx builders aligned with on-chain PDAs; TransferForm token mapping + transfer record fields; FaucetPanel shows Solana when RPC/dev; explorer + airdrop UX. Frontend build passes. Verified Settings → Faucet tab in dev server.

Frontend #67: Solana deposit_native + withdraw_submit tx builders aligned with on-chain PDAs; TransferForm token mapping + transfer record fields; FaucetPanel shows Solana when RPC/dev; explorer + airdrop UX. Frontend build passes. Verified Settings → Faucet tab in dev server.
Brouie commented 2026-03-26 03:40:52 +00:00 (Migrated from gitlab.com)

Pulled latest security push. New code reviewed: rate_limit.rs (24h rolling window, 8 unit tests), decimal.rs (normalize with proptest 4096 cases), nonce_used.rs (replay protection PDA), deposit_withdraw.test.ts (full flows moved from Rust E2E to anchor). 173/179 solana-test pass — 6 fail due to rate limit not configured in existing tests (#76). make solana-test-e2e broken by Bad substitution + test restructuring (#77). 3 new Playwright specs for Solana transfers (need browser). Operator migration 012 adds terra transfer_hash for Terra-to-Solana routing.

Pulled latest security push. New code reviewed: rate_limit.rs (24h rolling window, 8 unit tests), decimal.rs (normalize with proptest 4096 cases), nonce_used.rs (replay protection PDA), deposit_withdraw.test.ts (full flows moved from Rust E2E to anchor). 173/179 solana-test pass — 6 fail due to rate limit not configured in existing tests (#76). make solana-test-e2e broken by Bad substitution + test restructuring (#77). 3 new Playwright specs for Solana transfers (need browser). Operator migration 012 adds terra transfer_hash for Terra-to-Solana routing.
Brouie commented 2026-03-27 02:02:27 +00:00 (Migrated from gitlab.com)

Frontend QA pass (laptop, Chrome, dark mode, v0.1.140):

VERIFIED:

  • Solana Localnet appears as source/dest chain in bridge form
  • TransferForm populates correctly for Solana->EVM direction
  • Fee calculation: 1 SOL shows 0.5% fee, receive 0.995000
  • Route validation: shows clear error when dest token mapping not registered
  • BRIDGE FROM SOLANA button renders
  • FaucetPanel: Solana section visible, airdrop message shown
  • Settings: Solana Localnet chain card shows green connection 104ms
  • Settings: chain cards display RPC URLs correctly

ISSUES:

  • SOL airdrop button: times out after 30s, no Phantom sign popup
  • SOL balance shows '--' in header (WebSocket port 8900 not forwarded locally — need to verify with forwarding)
  • Token faucets show 'Not deployed' on BNB/opBNB (EVM port forwarding down — local env issue, not code bug)
  • Console: heavy WebSocket spam to ws://localhost:8900 (Solana WS port)

REMAINING:

  • Frontend: Solana->EVM deposit end-to-end (need SOL balance + airdrop working)
  • Frontend: EVM->Solana withdrawal end-to-end (need EVM port forwarding)
  • TransferStatusPage for Solana transfers
  • FaucetPanel: SPL token claim after faucet deploy
  • Canceler log verification (CLI)
Frontend QA pass (laptop, Chrome, dark mode, v0.1.140): VERIFIED: - [x] Solana Localnet appears as source/dest chain in bridge form - [x] TransferForm populates correctly for Solana->EVM direction - [x] Fee calculation: 1 SOL shows 0.5% fee, receive 0.995000 - [x] Route validation: shows clear error when dest token mapping not registered - [x] BRIDGE FROM SOLANA button renders - [x] FaucetPanel: Solana section visible, airdrop message shown - [x] Settings: Solana Localnet chain card shows green connection 104ms - [x] Settings: chain cards display RPC URLs correctly ISSUES: - SOL airdrop button: times out after 30s, no Phantom sign popup - SOL balance shows '--' in header (WebSocket port 8900 not forwarded locally — need to verify with forwarding) - Token faucets show 'Not deployed' on BNB/opBNB (EVM port forwarding down — local env issue, not code bug) - Console: heavy WebSocket spam to ws://localhost:8900 (Solana WS port) REMAINING: - [ ] Frontend: Solana->EVM deposit end-to-end (need SOL balance + airdrop working) - [ ] Frontend: EVM->Solana withdrawal end-to-end (need EVM port forwarding) - [ ] TransferStatusPage for Solana transfers - [ ] FaucetPanel: SPL token claim after faucet deploy - [ ] Canceler log verification (CLI)
Brouie commented 2026-03-27 02:05:43 +00:00 (Migrated from gitlab.com)

CLI verification:

  • cl8y_faucet program builds with anchor build (both programs compile)
  • setup-test-tokens.sh runs but creates 0 tokens — uses placeholder faucet ID (CL8YFaucet1111...), not deployed program ID GDRbn9sCxgUeGYoHfS3S24egcDF9HjXtX94zjH4u849K
  • SOLANA_COMMITMENT config exists in operator (defaults to 'finalized')
  • Operator creates Solana watcher with correct program ID when SOLANA_RPC_URL set
  • Operator does NOT skip Solana watcher when SOLANA_RPC_URL unset — reads from .deploy/local.env regardless (finding: should skip cleanly)

CLI items done. Remaining are end-to-end transfer flows (need EVM port forwarding + SOL balance).

CLI verification: - [x] cl8y_faucet program builds with anchor build (both programs compile) - [x] setup-test-tokens.sh runs but creates 0 tokens — uses placeholder faucet ID (CL8YFaucet1111...), not deployed program ID GDRbn9sCxgUeGYoHfS3S24egcDF9HjXtX94zjH4u849K - [x] SOLANA_COMMITMENT config exists in operator (defaults to 'finalized') - [x] Operator creates Solana watcher with correct program ID when SOLANA_RPC_URL set - [ ] Operator does NOT skip Solana watcher when SOLANA_RPC_URL unset — reads from .deploy/local.env regardless (finding: should skip cleanly) CLI items done. Remaining are end-to-end transfer flows (need EVM port forwarding + SOL balance).
PlasticDigits commented 2026-03-27 03:19:36 +00:00 (Migrated from gitlab.com)

QA on SSH-only VPS: local port forwarding (Solana + EVM) without breaking CSP / security

This documents the intended setup when chains run on a locked-down VPS (SSH only) and the browser runs on your laptop (e.g. npm run dev at http://localhost:5173). It matches how the frontend is built today and avoids the issues called out in recent #67 QA (missing 8900, EVM RPC unreachable, SOL balance --).

1. Why SSH -L and not the VPS public IP in .env

The app’s Content-Security-Policy (packages/frontend/index.html) allows connect-src to http(s)://localhost:* and http(s)://127.0.0.1:* and ws(s):// to the same — not arbitrary http://<vps-ip>:…. If you put the VPS hostname or public IP in VITE_* RPC URLs, the browser will block those requests even if the network path worked.

Do this instead: On the laptop, open local forwards so the browser keeps using http://127.0.0.1:… / ws://127.0.0.1:… (or localhost). Traffic then goes over SSH to 127.0.0.1 on the VPS where validators listen.

That does not weaken CORS for the app: you are not opening new public RPC ports; only your SSH session bridges them. RPC nodes still only accept connections from the VPS loopback (or your existing bind config).

2. Ports to forward (typical local stack)

Service Local forward (example) Notes
Solana HTTP RPC 8899 → 127.0.0.1:8899 Required for JSON-RPC (getBalance, txs).
Solana WebSocket 8900 → 127.0.0.1:8900 Required — @solana/web3.js Connection uses subscriptions; balance/UI often needs WS. Forwarding 8899 alone explains -- balance and WS noise in the console.
EVM (Anvil) 8545 → 127.0.0.1:8545 Matches BRIDGE_CHAINS.local.anvil / VITE_EVM_RPC_URL defaults.
EVM second chain (Anvil1) 8546 → 127.0.0.1:8546 Only if you use dual-anvil / anvil1 in the UI.
Terra 1317, 26657 If you test Terra + local LCD/Tendermint.

Example one SSH session (adjust user/host):

ssh -N \
  -L 8899:127.0.0.1:8899 \
  -L 8900:127.0.0.1:8900 \
  -L 8545:127.0.0.1:8545 \
  -L 8546:127.0.0.1:8546 \
  -L 1317:127.0.0.1:1317 \
  -L 26657:127.0.0.1:26657 \
  user@vps

Keep this session open while testing. Use ServerAliveInterval if idle drops are a problem:

ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -N ...

3. Environment variables on the laptop (frontend)

Use the same logical URLs as local all-in-one dev, pointing at 127.0.0.1 after the tunnel is up:

  • VITE_NETWORK=local
  • VITE_SOLANA_RPC_URL=http://127.0.0.1:8899 (or http://localhost:8899 — equivalent once forwarded)
  • VITE_SOLANA_PROGRAM_ID, bridge/token addresses, etc. — must match what is deployed on the VPS (copy from the server’s deploy output / .env, not assumed placeholders).
  • VITE_EVM_RPC_URL=http://127.0.0.1:8545 (and VITE_EVM1_… / second RPC if you use Anvil1 — align with 8546 forward).
  • VITE_TERRA_LCD_URL / VITE_TERRA_RPC_URL if testing Terra — http://127.0.0.1:1317 and http://127.0.0.1:26657 when tunneled.

Optional: VITE_SOLANA_FAUCET_ADDRESS, mint envs — only if the faucet program is deployed and registered on that same cluster.

Do not set frontend RPC URLs to https://api.devnet.solana.com or public BSC RPCs if your goal is to hit the VPS localnet — that would test a different network than the operator/validator on the VPS.

Rebuild or restart Vite after changing VITE_* (npm run dev picks them at startup).

4. CORS, rate limiting, and operator API

  • Solana / Anvil / Terra RPC: Browser calls go to your laptop’s localhost, which SSH forwards to the VPS. This stays within CSP connect-src and does not require exposing RPC on 0.0.0.0 publicly.
  • Operator HTTP API (if the UI or tools call it): rate limits (RATE_LIMIT_PER_SECOND / RATE_LIMIT_BURST_SIZE on the operator) apply per client IP as seen by the operator. Traffic forwarded via SSH typically appears from 127.0.0.1 on the server side — normal for a single tester. This is unrelated to Solana/EVM JSON-RPC ports unless you explicitly point the frontend at the operator URL.
  • No need to relax CSP for the tunnel pattern: keep RPC URLs on 127.0.0.1 / localhost.

5. If something still fails after tunnels + env

  • SOL balance still --: Confirm 8900 is forwarded and that nothing else is bound to 8900 on the laptop; check DevTools → Network / console for WebSocket errors to ws://127.0.0.1:8900.
  • Phantom / airdrop timeout: Often RPC path or chain mismatch; confirm program id + cluster match VITE_SOLANA_RPC_URL.
  • Separate CSP / WS policy issues were tracked elsewhere (e.g. #75); forwarding fixes “wrong host / port,” not policy bugs.

This should make laptop + SSH-only VPS QA behave like local frontend against remote validators without breaking security assumptions baked into the frontend.

### QA on SSH-only VPS: local port forwarding (Solana + EVM) without breaking CSP / security This documents the intended setup when chains run on a **locked-down VPS** (SSH only) and the **browser runs on your laptop** (e.g. `npm run dev` at `http://localhost:5173`). It matches how the frontend is built today and avoids the issues called out in recent #67 QA (missing **8900**, EVM RPC unreachable, SOL balance `--`). #### 1. Why SSH `-L` and not the VPS public IP in `.env` The app’s **Content-Security-Policy** (`packages/frontend/index.html`) allows `connect-src` to **`http(s)://localhost:*` and `http(s)://127.0.0.1:*`** and `ws(s)://` to the same — **not** arbitrary `http://<vps-ip>:…`. If you put the VPS hostname or public IP in `VITE_*` RPC URLs, the browser will **block** those requests even if the network path worked. **Do this instead:** On the laptop, open **local** forwards so the browser keeps using **`http://127.0.0.1:…` / `ws://127.0.0.1:…`** (or `localhost`). Traffic then goes over SSH to `127.0.0.1` on the VPS where validators listen. That does **not** weaken CORS for the app: you are not opening new public RPC ports; only your SSH session bridges them. RPC nodes still only accept connections from the VPS loopback (or your existing bind config). #### 2. Ports to forward (typical local stack) | Service | Local forward (example) | Notes | |--------|-------------------------|--------| | **Solana HTTP RPC** | `8899 → 127.0.0.1:8899` | Required for JSON-RPC (`getBalance`, txs). | | **Solana WebSocket** | `8900 → 127.0.0.1:8900` | **Required** — `@solana/web3.js` `Connection` uses subscriptions; balance/UI often needs WS. Forwarding **8899 alone** explains **`--` balance** and WS noise in the console. | | **EVM (Anvil)** | `8545 → 127.0.0.1:8545` | Matches `BRIDGE_CHAINS.local.anvil` / `VITE_EVM_RPC_URL` defaults. | | **EVM second chain (Anvil1)** | `8546 → 127.0.0.1:8546` | Only if you use dual-anvil / `anvil1` in the UI. | | **Terra** | `1317`, `26657` | If you test Terra + local LCD/Tendermint. | Example **one SSH session** (adjust user/host): ```bash ssh -N \ -L 8899:127.0.0.1:8899 \ -L 8900:127.0.0.1:8900 \ -L 8545:127.0.0.1:8545 \ -L 8546:127.0.0.1:8546 \ -L 1317:127.0.0.1:1317 \ -L 26657:127.0.0.1:26657 \ user@vps ``` Keep this session **open** while testing. Use `ServerAliveInterval` if idle drops are a problem: ```bash ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -N ... ``` #### 3. Environment variables on the **laptop** (frontend) Use the **same logical URLs as local all-in-one dev**, pointing at **127.0.0.1** after the tunnel is up: - **`VITE_NETWORK=local`** - **`VITE_SOLANA_RPC_URL=http://127.0.0.1:8899`** (or `http://localhost:8899` — equivalent once forwarded) - **`VITE_SOLANA_PROGRAM_ID`**, bridge/token addresses, etc. — **must match what is deployed on the VPS** (copy from the server’s deploy output / `.env`, not assumed placeholders). - **`VITE_EVM_RPC_URL=http://127.0.0.1:8545`** (and **`VITE_EVM1_…`** / second RPC if you use Anvil1 — align with `8546` forward). - **`VITE_TERRA_LCD_URL` / `VITE_TERRA_RPC_URL`** if testing Terra — `http://127.0.0.1:1317` and `http://127.0.0.1:26657` when tunneled. Optional: **`VITE_SOLANA_FAUCET_ADDRESS`**, mint envs — only if the faucet program is deployed and registered on that same cluster. **Do not** set frontend RPC URLs to `https://api.devnet.solana.com` or public BSC RPCs if your goal is to hit the **VPS localnet** — that would test a different network than the operator/validator on the VPS. Rebuild or restart Vite after changing `VITE_*` (`npm run dev` picks them at startup). #### 4. CORS, rate limiting, and operator API - **Solana / Anvil / Terra RPC:** Browser calls go to **your laptop’s localhost**, which SSH forwards to the VPS. This stays within **CSP `connect-src`** and does not require exposing RPC on `0.0.0.0` publicly. - **Operator HTTP API** (if the UI or tools call it): rate limits (`RATE_LIMIT_PER_SECOND` / `RATE_LIMIT_BURST_SIZE` on the operator) apply per **client IP as seen by the operator**. Traffic forwarded via SSH typically appears from **127.0.0.1** on the server side — normal for a single tester. This is unrelated to Solana/EVM JSON-RPC ports unless you explicitly point the frontend at the operator URL. - **No need to relax CSP** for the tunnel pattern: keep RPC URLs on **`127.0.0.1` / `localhost`**. #### 5. If something still fails after tunnels + env - **SOL balance still `--`:** Confirm **8900** is forwarded and that nothing else is bound to 8900 on the laptop; check DevTools → Network / console for **WebSocket** errors to `ws://127.0.0.1:8900`. - **Phantom / airdrop timeout:** Often RPC path or chain mismatch; confirm program id + cluster match `VITE_SOLANA_RPC_URL`. - **Separate CSP / WS policy issues** were tracked elsewhere (e.g. #75); forwarding fixes “wrong host / port,” not policy bugs. This should make laptop + SSH-only VPS QA behave like **local** frontend against **remote** validators without breaking security assumptions baked into the frontend.
PlasticDigits commented 2026-03-27 03:36:12 +00:00 (Migrated from gitlab.com)

QA server: DEX + bridge LocalTerra port conflict

On the shared QA host, cl8y-dex-terraclassic already binds LocalTerra on the default host ports (26657, 1317, 9090, 9091). Starting cl8y-bridge-monorepo docker-compose with the same mappings fails with port already allocated.

Mitigation: set alternate host ports for the bridge stack’s LocalTerra service via docker-compose env (e.g. E2E_TERRA_RPC_PORT, E2E_TERRA_LCD_PORT, E2E_TERRA_GRPC_PORT, E2E_TERRA_GRPC_WEB_PORT in a repo-root .env) so bridge and DEX can run side by side. Any bridge tooling, operator/canceler, make status, deploy scripts, and frontend VITE_TERRA_* / TERRA_* URLs must use the remapped host ports, not the defaults.

### QA server: DEX + bridge LocalTerra port conflict On the shared QA host, **cl8y-dex-terraclassic** already binds LocalTerra on the default host ports (**26657**, **1317**, **9090**, **9091**). Starting **cl8y-bridge-monorepo** `docker-compose` with the same mappings fails with **port already allocated**. **Mitigation:** set alternate host ports for the bridge stack’s LocalTerra service via `docker-compose` env (e.g. `E2E_TERRA_RPC_PORT`, `E2E_TERRA_LCD_PORT`, `E2E_TERRA_GRPC_PORT`, `E2E_TERRA_GRPC_WEB_PORT` in a repo-root `.env`) so bridge and DEX can run side by side. Any bridge tooling, operator/canceler, `make status`, deploy scripts, and frontend `VITE_TERRA_*` / `TERRA_*` URLs must use the **remapped** host ports, not the defaults.
PlasticDigits commented 2026-03-27 03:44:40 +00:00 (Migrated from gitlab.com)

Documentation: shared QA host tooling (branch feat/solana-integration)

This issue stays open as a running QA checklist; the following is reference only.

make start-qa / make stop-qa

  • make start-qa (see scripts/qa/start-qa.sh): Before anything else, tears down an existing bridge stack (canceler stop-all, operator stop, docker compose down). Then brings up Docker (Anvil, LocalTerra, Solana, Postgres), runs make operator-migrate, make deploy, writes .env.e2e.local and packages/frontend/.env.local from .deploy/local.env, starts operator and canceler with QA_SHARED_HOST=1, verifies operator and canceler /health, and prints a copy-paste ssh -N -L … line for the QA laptop (ports follow scripts/qa/qa-host.env). Optional: QA_SSH_DEST=user@host make start-qa to bake the SSH destination.

  • make stop-qa: Stops canceler, operator, and runs docker compose down.

Shared host / DEX + bridge port separation

  • scripts/qa/qa-host.env: Default remapped LocalTerra host ports (26658, 1318, 9092, 9093) and OPERATOR_API_PORT=9094 so Terra gRPC on 9092 does not collide with the operator API (default 9092 in code).
  • scripts/qa/write-qa-env-e2e.sh: Merges deploy addresses with QA URLs for .env.e2e.local (bash) and packages/frontend/.env.local (Vite).
  • scripts/deploy-terra-local.sh and setup-bridge.sh honor TERRA_RPC_URL / TERRA_LCD_URL so deploys target the bridge LocalTerra on remapped ports, not the DEX stack on 26657/1317.
  • scripts/operator-ctl.sh: When QA_SHARED_HOST=1, sources qa-host.env after .env so URLs and operator port win.

Docs

  • scripts/qa/README.md: Port conflict cat >> .env snippet, SSH / QA_SSH_DEST, and workflow notes.

Laptop QA (unchanged)

Browser + Vite still run on the laptop; SSH local forwards are required to reach 127.0.0.1 services on the QA server. make start-qa prints the exact ssh command at the end.


Commit: c2402fd on feat/solana-integration.

### Documentation: shared QA host tooling (branch `feat/solana-integration`) This issue stays **open** as a running QA checklist; the following is reference only. #### `make start-qa` / `make stop-qa` - **`make start-qa`** (see `scripts/qa/start-qa.sh`): Before anything else, **tears down** an existing bridge stack (canceler `stop-all`, operator `stop`, `docker compose down`). Then brings up Docker (Anvil, LocalTerra, Solana, Postgres), runs **`make operator-migrate`**, **`make deploy`**, writes **`.env.e2e.local`** and **`packages/frontend/.env.local`** from **`.deploy/local.env`**, starts **operator** and **canceler** with **`QA_SHARED_HOST=1`**, **verifies** operator and canceler **`/health`**, and prints a **copy-paste `ssh -N -L …`** line for the QA laptop (ports follow `scripts/qa/qa-host.env`). Optional: **`QA_SSH_DEST=user@host make start-qa`** to bake the SSH destination. - **`make stop-qa`**: Stops canceler, operator, and runs **`docker compose down`**. #### Shared host / DEX + bridge port separation - **`scripts/qa/qa-host.env`**: Default **remapped** LocalTerra host ports (**26658**, **1318**, **9092**, **9093**) and **`OPERATOR_API_PORT=9094`** so Terra gRPC on **9092** does not collide with the operator API (default **9092** in code). - **`scripts/qa/write-qa-env-e2e.sh`**: Merges deploy addresses with QA URLs for **`.env.e2e.local`** (bash) and **`packages/frontend/.env.local`** (Vite). - **`scripts/deploy-terra-local.sh`** and **`setup-bridge.sh`** honor **`TERRA_RPC_URL` / `TERRA_LCD_URL`** so deploys target the bridge LocalTerra on remapped ports, not the DEX stack on **26657/1317**. - **`scripts/operator-ctl.sh`**: When **`QA_SHARED_HOST=1`**, sources **`qa-host.env`** after **`.env`** so URLs and operator port win. #### Docs - **`scripts/qa/README.md`**: Port conflict **`cat >> .env`** snippet, SSH / **`QA_SSH_DEST`**, and workflow notes. #### Laptop QA (unchanged) Browser + **Vite** still run on the laptop; **SSH local forwards** are required to reach **127.0.0.1** services on the QA server. **`make start-qa`** prints the exact `ssh` command at the end. --- Commit: `c2402fd` on `feat/solana-integration`.
PlasticDigits commented 2026-03-27 08:51:51 +00:00 (Migrated from gitlab.com)

@brouie After make start-qa on the QA server, do manual frontend QA on your laptop in this order:

  1. SSH port forwards — Run the ssh -4 -N ... block printed at the end of make start-qa (keep that terminal open). Use 127.0.0.1 on both sides.

  2. Copy .deploy/local.env — From the QA host into your repo clone (use the exact scp line from the script output):
    scp …:.deploy/local.env .deploy/local.env

  3. Generate packages/frontend/.env.local —
    ./scripts/qa/write-frontend-env-local.sh
    (Restart Vite if it was already running so new VITE_* load.)

  4. Install deps and run Vite —
    cd packages/frontend && npm ci && npm run dev

  5. Open the URL Vite prints (e.g. http://localhost:3000).

WalletConnect: add VITE_WC_PROJECT_ID to packages/frontend/.env.local (get a project id from https://cloud.walletconnect.com) if you use WalletConnect; MetaMask / other injected wallets work without it.

More detail: scripts/qa/README.md → section On your laptop.

@brouie After **`make start-qa`** on the QA server, do **manual frontend QA on your laptop** in this order: 1. **SSH port forwards** — Run the **`ssh -4 -N ...`** block printed at the end of **`make start-qa`** (keep that terminal open). Use **`127.0.0.1`** on both sides. 2. **Copy `.deploy/local.env`** — From the QA host into your repo clone (use the exact **`scp`** line from the script output): `scp …:.deploy/local.env .deploy/local.env` 3. **Generate `packages/frontend/.env.local`** — `./scripts/qa/write-frontend-env-local.sh` (Restart Vite if it was already running so new **`VITE_*`** load.) 4. **Install deps and run Vite** — `cd packages/frontend && npm ci && npm run dev` 5. **Open the URL** Vite prints (e.g. `http://localhost:3000`). **WalletConnect:** add **`VITE_WC_PROJECT_ID`** to **`packages/frontend/.env.local`** (get a project id from https://cloud.walletconnect.com) if you use WalletConnect; MetaMask / other injected wallets work without it. More detail: **`scripts/qa/README.md`** → section **On your laptop**.
Brouie commented 2026-03-27 11:48:02 +00:00 (Migrated from gitlab.com)

E2E testing with make start-qa (2026-03-27, v0.1.183 / 4c02b63):

Setup:

  • make start-qa works clean, all 4 chains visible (Anvil, Anvil 2, LocalTerra, Solana Localnet)
  • Operator + canceler healthy
  • SOLANA_RPC_URL was missing from .env — operator skipped Solana watcher silently. Added manually, watcher now running. Suggest make start-qa auto-adds this.

Verified:

  • Phantom wallet connects, shows 1.34 SOL in deposit form
  • Solana->Anvil deposit submitted and signed by Phantom
  • Operator picks up Solana deposit after SOLANA_RPC_URL fix
  • TransferStatusPage redirects after deposit
  • Faucet airdrop button works (sends SOL)

Bugs found:

  • SOL balance always shows '--' in header despite Phantom connected with balance
  • TransferStatusPage shows 'Transfer Not Found' after deposit (operator may need time to index)
  • Faucet airdrop sends SOL to server keypair (12TC...PUgv), not connected Phantom wallet
  • No Solana tokens in faucet panel (related #78)
  • Port 8546 (Anvil 2) spamming ERR_CONNECTION_REFUSED in console
  • LUNC balance fetch polling too aggressively (useWallet.ts:151)

Remaining:

  • Confirm operator processes the Solana deposit end-to-end
  • EVM->Solana withdrawal
  • TransferStatusPage showing correct status for Solana transfers
E2E testing with make start-qa (2026-03-27, v0.1.183 / 4c02b63): Setup: - make start-qa works clean, all 4 chains visible (Anvil, Anvil 2, LocalTerra, Solana Localnet) - Operator + canceler healthy - SOLANA_RPC_URL was missing from .env — operator skipped Solana watcher silently. Added manually, watcher now running. Suggest make start-qa auto-adds this. Verified: - [x] Phantom wallet connects, shows 1.34 SOL in deposit form - [x] Solana->Anvil deposit submitted and signed by Phantom - [x] Operator picks up Solana deposit after SOLANA_RPC_URL fix - [x] TransferStatusPage redirects after deposit - [x] Faucet airdrop button works (sends SOL) Bugs found: - SOL balance always shows '--' in header despite Phantom connected with balance - TransferStatusPage shows 'Transfer Not Found' after deposit (operator may need time to index) - Faucet airdrop sends SOL to server keypair (12TC...PUgv), not connected Phantom wallet - No Solana tokens in faucet panel (related #78) - Port 8546 (Anvil 2) spamming ERR_CONNECTION_REFUSED in console - LUNC balance fetch polling too aggressively (useWallet.ts:151) Remaining: - [ ] Confirm operator processes the Solana deposit end-to-end - [ ] EVM->Solana withdrawal - [ ] TransferStatusPage showing correct status for Solana transfers
Brouie commented 2026-03-28 02:19:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #81

mentioned in issue #81
Brouie commented 2026-03-30 06:14:20 +00:00 (Migrated from gitlab.com)

FIRST FULL E2E TRANSFER COMPLETE: LocalTerra -> Anvil, 298.50 KDEC. All 4 steps passed — deposit, submit hash (auto-submit to EVM), operator approval, tokens delivered. TransferStatusPage shows 4/4 DONE correctly. Destination TX confirmed (0x96ec07...).

EVM->Terra direction (Anvil->LocalTerra) blocked by Keplr 'Failed to fetch balance' on LocalTerra — Keplr can't verify wallet balance on localnet chain config. Not a bridge code bug, QA environment issue. Will investigate Keplr chain config.

Anvil1 direction untestable due to #85 (Anvil1 0/7 registration).
Solana direction untestable due to #84 (wSOL mint missing).

FIRST FULL E2E TRANSFER COMPLETE: LocalTerra -> Anvil, 298.50 KDEC. All 4 steps passed — deposit, submit hash (auto-submit to EVM), operator approval, tokens delivered. TransferStatusPage shows 4/4 DONE correctly. Destination TX confirmed (0x96ec07...). EVM->Terra direction (Anvil->LocalTerra) blocked by Keplr 'Failed to fetch balance' on LocalTerra — Keplr can't verify wallet balance on localnet chain config. Not a bridge code bug, QA environment issue. Will investigate Keplr chain config. Anvil1 direction untestable due to #85 (Anvil1 0/7 registration). Solana direction untestable due to #84 (wSOL mint missing).
Brouie commented 2026-03-30 06:21:57 +00:00 (Migrated from gitlab.com)

Second E2E direction confirmed: Anvil->LocalTerra 99.50 TKNA, withdraw_submit succeeded on Terra (txHash D668A506). Auto-withdraw retry bug makes UI show FAILED but the on-chain submission worked — tracked in #87. Both EVM<->Terra directions now verified working at the contract/operator level.

Second E2E direction confirmed: Anvil->LocalTerra 99.50 TKNA, withdraw_submit succeeded on Terra (txHash D668A506). Auto-withdraw retry bug makes UI show FAILED but the on-chain submission worked — tracked in #87. Both EVM<->Terra directions now verified working at the contract/operator level.
PlasticDigits commented 2026-03-30 07:49:22 +00:00 (Migrated from gitlab.com)

@brouie Please prioritize sol<>anvil/anvil1 and sol<>localterra paths.

@brouie Please prioritize sol<>anvil/anvil1 and sol<>localterra paths.
Brouie commented 2026-03-31 08:57:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #92

mentioned in issue #92
Brouie commented 2026-04-01 04:55:07 +00:00 (Migrated from gitlab.com)

closing -- Solana->Anvil E2E verified via Backpack. Terra->Anvil, Terra->Anvil1, Anvil->Anvil1 all complete. Remaining paths (Anvil->Terra blocked by #89, Terra->Solana by #94) consolidated into new combined Solana Integration QA issue.

closing -- Solana->Anvil E2E verified via Backpack. Terra->Anvil, Terra->Anvil1, Anvil->Anvil1 all complete. Remaining paths (Anvil->Terra blocked by #89, Terra->Solana by #94) consolidated into new combined Solana Integration QA issue.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-01 04:55:10 +00:00
Brouie commented 2026-04-01 04:55:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #96

mentioned in issue #96
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#67
No description provided.