feat(frontend): gate bridge.cl8y.com with CL8Y Legal web clickwrap #134
Labels
No labels
agent:implement
agent:ready
backend
bug
cannot-reproduce
confirmed
desktop
docs
documentation
duplicate
enhancement
feature
frontend
good first issue
help wanted
high-risk
in-review
invalid
mobile
needs-triage
P0-critical
P1-high
P2-medium
P3-low
qa
QA
question
ready
report
responsive
security
security-escalate
smart-contract
solana
tablet
test-pass
ux
wallet-issue
wallet:keplr
wallet:metamask
wallet:station
wallet:walletconnect
wontfix
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-bridge-monorepo#134
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Integrate
@plasticdigits/cl8y-clickwrapintopackages/frontendso wallet-connected users on https://bridge.cl8y.com must accept the latest CL8Y Terms & Conditions (via the hosted portal at https://terms.cl8y.com) before they can use transfer / deposit / withdraw UI. Bundle dependency install, multi-network gating (EVM / Terra Classic / Solana), UX wiring, ops prerequisites, docs/invariants, and tests into this single issue.Upstream SDK / portal: cl8y-ecosystem-legal — package
packages/cl8y-clickwrap, APIhttps://api.terms.cl8y.com, portalhttps://terms.cl8y.com.Current codebase
The bridge frontend (
packages/frontend) is a Vite + React 18 app with:src/main.tsx—BrowserRouter+Layoutoutlet; routes/,/transfer/:xchainHashId,/history,/verify,/settingsuseAccount) viaConnectWallet/EvmWalletModal. Terra Classic:useWallet/stores/wallet. Solana:useSolanaWallet/stores/solanaWalletTransferFormalready requires the source-chain wallet (isWalletConnected≈ Terra / Solana / EVM bydirection) before deposit CTAs — but there is no legal terms / signature-status check@plasticdigits/cl8y-clickwrap, no calls toapi.terms.cl8y.com, no redirect toterms.cl8y.comdocs/FRONTEND_BRIDGE_INVARIANTS.md; Playwright underpackages/frontend/e2e/; Vitest unit/integration undersrc/**CL8Y Legal model (upstream):
cl8y.comdoes not satisfybridge.cl8y.com.(property, terms version, network, account).GET /api/v1/signatures/status(SDK:client.getSignatureStatus), then redirect unsigned users to the portal sign URL for that network.SDK surface (intended):
TermsGatepolls signature status, shows accept UI whensigned_latestis false, redirects to the portal withredirect_uri+app_name, and re-checks on window focus after return.Package availability note: As of this issue,
@plasticdigits/cl8y-clickwrap@0.1.0may not yet be on the public npm registry. Prefer published npm once available; until then, coordinate with Legal to publish, or temporarily install from the Legal repo tarball / git path (document the chosen path in the MR).Why this is needed
signed_latestbecomes false and the bridge must re-gate until the user re-signs — without a custom versioning system in this monorepo.Constraints & guardrails
Property hostname: Register and use property
bridge.cl8y.com(exact production host). Do not reusecl8y.comacceptance. Confirm with Legal ops before merge.CORS: Legal API
CORS_ORIGINSmust includehttps://bridge.cl8y.com(and local Vite origin for dev, e.g.http://localhost:3000). Browser status checks will fail closed without this.Portal
redirect_uriallowlist: Legal webVITE_REDIRECT_URI_ALLOWLISTmust includehttps://bridge.cl8y.comso post-sign navigation returns to the bridge. Localhost only when Legal enablesVITE_ALLOW_LOCALHOST_REDIRECT.Prefer SDK, do not reimplement: Use
@plasticdigits/cl8y-clickwrap(createClient,TermsGateand/oruseSignatureStatus+buildSignUrl). Do not copy portal sign-page DOM, do not invent a parallel local “I agree” checkbox that claims to satisfy Legal, and do not submit wallet signatures from the bridge unless Legal explicitly documents that path for integrators.Network mapping must match source wallet (same idea as
TransferForm’sisWalletConnected):NetworkEVMaddressTerraClassicaddressSolanaaddressNo account → no hard block of connect UX: When no relevant wallet is connected, users must still be able to open wallet modals / browse read-only surfaces. Gate mutative bridge actions (deposit CTA, and any withdraw/execute paths that require the connected account) — not the entire app shell — unless product explicitly decides otherwise. Recommended default: wrap transfer action surface (and equivalent status-page submit/execute panels) so unsigned users see the TermsGate / accept CTA instead of deposit controls.
UI consent is not auth: UI gate + portal redirect prove UX intent; server-side Legal still requires a valid wallet signature. Do not treat a local checkbox alone as acceptance.
Safe rendering: Never
dangerouslySetInnerHTMLfor terms API content or query params. Prefer portal for full text; if linking toGET .../terms/latest/content, open as plain text / external link.Redirect safety: Pass
redirectUri={window.location.href}(or a same-origin path) only. Optionally preflight with SDKisAllowedRedirectUrifor fail-fast UX; portal remains the enforcer.Fail closed on status errors for mutative actions: If signature status cannot be verified (network/CORS/5xx), do not silently enable Bridge / deposit / execute. Show a clear error + retry.
Do not break existing frontend invariants in
docs/FRONTEND_BRIDGE_INVARIANTS.md(recipient validation INV-RCP1, CTA validity INV-UX1, rate-limit UX, MegaETH chip, token logos, etc.).Under-construction mode: When
VITE_UNDER_CONSTRUCTION === 'true', clickwrap is N/A (onlyUnderConstructionPage).Secrets: No new secret
VITE_*keys. Public API base URLs only if overridden; default SDK bases (api.terms.cl8y.com/terms.cl8y.com) are fine for production.Solana portal readiness: Confirm Legal portal
/sign/solanais live for production before requiring Solana source users to complete the redirect loop. If Solana portal is not ready, document a temporary product decision in the MR (block Solana deposits with explicit message vs delay Solana gating) — do not silently skip legal for Solana while gating EVM/Terra.AGPL / npm license: Bridge frontend is part of an AGPL monorepo; SDK is MIT. Adding an npm dependency is fine; do not vendor Legal server code into this repo.
Relevant files (bridge monorepo)
packages/frontend/package.json@plasticdigits/cl8y-clickwrap(+ lockfile)packages/frontend/src/main.tsxpackages/frontend/src/components/Layout.tsxpackages/frontend/src/components/transfer/TransferForm.tsxisWalletConnected; best place to enforce signed-latest for source walletpackages/frontend/src/pages/TransferStatusPage.tsxpackages/frontend/src/hooks/useWallet.tspackages/frontend/src/components/ConnectWallet.tsxpackages/frontend/src/stores/solanaWallet.ts(and Solana connect hooks)packages/frontend/src/utils/validateEnv.ts/.env.examplepackages/frontend/e2e/*.spec.tsdocs/FRONTEND_BRIDGE_INVARIANTS.mdpackages/frontend/README.md/docs/frontend.mdUpstream coordination (Legal repo — track as checklist, not necessarily implemented here):
bridge.cl8y.comscripts/register-property.sh)https://bridge.cl8y.comto APICORS_ORIGINShttps://bridge.cl8y.comto portalVITE_REDIRECT_URI_ALLOWLISTand redeploy web@plasticdigits/cl8y-clickwrapto npm (or approve tarball pin)Recommended solution direction
@plasticdigits/cl8y-clickwraptopackages/frontend. Share onecreateClient()instance (module singleton or React context) with production defaults.useBridgeClickwrapAccount()that, given current source chain type (or the wallet required for the active mutative action), returns{ network, account }from the correct wallet store.TransferForm, whenisWalletConnectedand account known: wrap deposit CTA / form action region withTermsGate(oruseSignatureStatus+ bridge-styled unsigned panel that calls the portal).TransferStatusPage, gate withdraw-submit / execute controls the same way for the wallet that must sign.unsignedslot that shows version label / effective date (fromgetTermsLatest) and a primary “Accept terms” button that redirects viabuildSignUrl/TermsGateaccept handler.appNamee.g."CL8Y Bridge".redirectUri= current page URL so users return to the same transfer/status deep link.useSignatureStatus); ensure deposit CTA enables only whensigned_latest === true.docs/FRONTEND_BRIDGE_INVARIANTS.mdand a short agent/dev note if the repo pattern warrants a skill underskills/.ClickwrapClientin Vitest; use real Legal staging only in optional manual QA.Acceptance criteria
@plasticdigits/cl8y-clickwrapis a declared dependency ofpackages/frontendand builds cleanly (tsc+vite build).bridge.cl8y.com.signed_latest === false, deposit/bridge CTA is blocked and user can navigate to the Legal portal to sign; after successful sign + return, status re-checks and CTA unblocks without full manual cache clear.Network: TerraClassic).Network: Solana), or an explicit documented interim policy if Solana portal is not ready (must not silently skip).dangerouslySetInnerHTMLfor Legal content; no open redirect via attacker-controlledredirect_uriconstructed by the bridge.docs/FRONTEND_BRIDGE_INVARIANTS.mddocuments the clickwrap invariant; frontend README mentions the integration.make test-frontend/ package scripts).Test plan — functional paths
Unit / component (Vitest + Testing Library)
ClickwrapClient.getSignatureStatus→signed_latest: true→ children / deposit affordance render.signed_latest: false→ unsigned / accept UI; children mutative controls hidden or disabled.EVM; Terra address →TerraClassic; Solana address →Solana.property=bridge.cl8y.com, correct network sign path,redirect_uri(current origin), andapp_name.Integration / manual QA
/transfer/:xchainHashIdor/, ensureredirect_urirestores that URL.VITE_UNDER_CONSTRUCTION=trueunchanged (no clickwrap requirement on that page).Playwright (
packages/frontend/e2e)signed_latest: trueso existing transfer e2e is not flaky on external Legal uptime.@legal) — not required in default CI if external dependency is unstable.Test plan — attack, hack & abuse vectors
signed_latestvia service worker / fake response in victim browsersigned=1redirect_uriwhen building sign URLwindow.location.href(or fixed allowlisted path); portal allowlist rejects othershttps://evil.examplein a patched build and confirm portal does not navigateapp_nameorpropertyquery reflection on portalappName/propertybridge.cl8y.combridge.cl8y.comapiBaseUrl/termsBaseUrloverrides in MR*NetworkEVMstatuscl8y.comvsbridge.cl8y.com)bridge.cl8y.comhttp://API in prod@plasticdigits/cl8y-clickwrap; verify publish integritynpm ls, lockfile pinVerification criteria
Merge is verified when all of the following hold:
packages/frontendtypechecks and production-builds with the new dependency.bridge.cl8y.comregistered; CORS + redirect allowlist deployed; npm package published (or approved alternate install documented).Out of scope
References
packages/cl8y-clickwrap/README.mdportal-sign-disclosure,security-opsmentioned in issue #137
marked as related to #137
mentioned in commit
3553667f0ementioned in merge request !156
mentioned in merge request !159
mentioned in commit
9bc7d3951amentioned in commit
a5a7620ccfMerge status (!156 landed on
main)Code from !156 is merged. Do not close this issue yet — production gating is still blocked outside this repo.
Remaining (issue AC / verification)
bridge.cl8y.comCORS_ORIGINSincludeshttps://bridge.cl8y.com(andhttp://localhost:3000for local)VITE_REDIRECT_URI_ALLOWLISTincludeshttps://bridge.cl8y.com/sign/solanaproduction-ready (envelope vs API verify). Solana stays fail-closed untilsigned_latest— do not skip.terms.cl8y.com→ return → deposit CTALanded in merge
SDK
@plasticdigits/cl8y-clickwrap@0.1.1, propertybridge.cl8y.com, mutative CTA gate, fail-closed on API error, header Connect outside TermsGate.Post-merge: e2e specs that still used accessible name
CONNECT TCwere updated to the GL-137 header locator (Connect Terra Wallet/data-testid=connect-terra-wallet).Post-merge e2e follow-up on
main(a211a73): clickwrap/validation/dev-wallet specs now use the GL-137 header locator (headerTerraConnect/Connect Terra Wallet). Playwrighte2e/wallet-connect.spec.ts+e2e/clickwrap.spec.tspassed locally with 5 workers (12/12). Legal ops boxes above still block production deploy./agent implement
cl8y-agent-control: queued
implementjoba090f0ff-0711-4f5b-8561-7a05a52c2c9f(not executed; no Hetzner VM).