bug(web): EVM T&C sign requires injected window.ethereum — fails in Binance Web3 and MetaMask iOS #15
Labels
No labels
agent:implement
agent:ready
api
bot
bug
ci
enhancement
ready
security
terra-classic
testing
ux
web
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-ecosystem-legal#15
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
Community report (2026-08-30): a mobile user on production
https://terms.cl8y.com/sign/evmcompleted the terms scroll + consent checkbox, then Connect & sign showed:Error: No EVM wallet found (install MetaMask or similar)The same error reproduced when the reporter tried Binance Web3 Wallet and again when trying MetaMask on iOS. Screenshot of the hosted portal matches
web/src/pages/evm.ts: consent is checked, CTA is enabled, andString(new Error(…))is rendered in the status region.This is the EVM analogue of #9 (Terra Classic dead-ended on
Keplr extension not foundin mobile Chrome). Terra now has injected multi-wallet + Open in Keplr + in-page WalletConnect (#11). EVM still has none of that.Safari / Chrome / Firefox on a phone do not inject an EIP-1193 provider. MetaMask iOS and Binance Web3 only inject inside their in-app dApp browsers (or via WalletConnect). The portal currently treats a missing
window.ethereumas a hard failure and tells the user to install a desktop extension.Workaround that may work today: paste the portal URL into MetaMask or Binance Web3’s in-app browser. Retail users opening
sign_urls.evmfrom an integrator in the system browser never get that instruction — they only get the error.Current codebase
Portal EVM sign (
web/src/pages/evm.ts):onSignstarts withif (!window.ethereum) throw new Error("No EVM wallet found (install MetaMask or similar)");createWalletClient({ chain: mainnet, transport: custom(window.ethereum) }),requestAddresses(),signMessage(EIP-191personal_signvia viem).POST /api/v1/signatures/walletwithnetwork: "EVM". Server verifies EIP-191 recovery inapi/src/verify/evm.rs.renderSignShellis used;extraControlsis omitted. Comments inweb/src/signShell.tsandskills/portal-sign-disclosure/SKILL.mdcurrently say EVM must omit extraControls (Terra-only Keplr / WC).There is no:
eip6963:announceProvider)window.ethereum.providers[],window.BinanceChain, or delayedethereum#initializedpersonal_signfor EVMaccount=0x…(getClaimedAccountexists but only Terra uses it)Playwright EVM coverage (
web/e2e/helpers/evm-wallet.ts,web/e2e/evm-sign.spec.ts) injectswindow.ethereumup front.web/e2e/sign-pages.spec.tsasserts EVM has no Open in Keplr — it does not assert a missing-provider CTA. The missing-wallet path is untested.Integrators (
@plasticdigits/cl8y-clickwrapTermsGate) only redirect tosign_urls.evm. They do not implement EVM connect. Every CL8Y property that sends EVM users to this portal inherits the dead-end.Gap analysis already lists this:
gaps/GAP_1786322222.mditems 10–11 (WalletConnect / EIP-6963 / mobile deep links) — “other networks still extension-only.”Why this is needed
sign_urls.evmstrands phone users after they already read and agreed to the terms.window.ethereumcan be late, EIP-6963-only, or exposed aswindow.BinanceChain/ethereum.providers. A single synchronouswindow.ethereumcheck fails those environments too.Constraints / guardrails
api/src/verify/evm.rs. WalletConnect / in-app browsers must still produce a 65-bytepersonal_signthe existing verifier accepts (recovered address = claimedaccount_id). Noeth_signof a raw hash. No silent switch to EIP-712 / SIWE without a versioned API change and golden tests.api/src/message.rs,packages/cl8y-clickwrap/src/message.ts). Messages must keepContent-SHA256(#6).EVM. Do not retarget a specific L2 chain id for the legal message;personal_signis chain-agnostic.createWalletClient({ chain: mainnet })is only a viem default — do not require the wallet to be on Ethereum mainnet if that blocks Binance / other EVM wallets from signing an off-chain message.VITE_WC_PROJECT_ID(already required for Galaxy Station). Do not copy DEX / ustr-cmm Cloud ids.account_id(lowercase0x+ 40 hex). If the integrator passesaccount=0x…on the query string (getClaimedAccount/ SDKbuildSignUrl({ account })), a different connected address is a hard fail (Terra #11 continuity).web/src/redirect.ts, #3). Deep-link / WC return URLs must encode this portal sign URL (origin + path + search), never query-suppliedredirect_uri. Unknown schemes fail closed.isAllowedWalletConnectDeepLinkfor Terra): only documented MetaMask / Binance Web3 /wc:/https:portal URLs. Nojavascript:, no arbitraryhttps://from wallet payloads.location.hrefafter an await). Copy-link copies the portal URL (or rawwc:URI for pairing), not a third-party wrapper that can drop the query string.skills/portal-sign-disclosure/SKILL.md): full terms on-page, scroll-to-bottom, checkbox, then wallet. Wallet APIs must not run before consent.signShellmust still resetbusyon throw and early return (#10).innerHTMLwallet names, pairing URIs, or query values.signed_latest. Client must not skipPOST /signatures/wallet.signShell.tscomments andskills/portal-sign-disclosure/SKILL.mdwhen EVM gains CTAs — that line is now wrong, not a reason to skip the fix.personal_sign+ existing submit path. Prefer the Terra pattern: small portal-owned modules, mockable in Playwright.Relevant files
web/src/pages/evm.tsweb/src/signShell.ts,web/src/ui.tsaccount,redirect_uri)web/src/query.tsweb/src/redirect.tsweb/src/keplrMobile.ts,web/src/keplrMobileUi.ts,web/src/terra/walletConnect*.ts,web/src/terra/injected.tsapi/src/verify/evm.rsapi/src/routes/signatures.rsapi/src/message.rs,packages/cl8y-clickwrap/src/message.tspackages/cl8y-clickwrapskills/portal-sign-disclosure/SKILL.mdskills/testing-coverage/SKILL.mdweb/e2e/evm-sign.spec.ts,web/e2e/sign-pages.spec.ts,web/e2e/helpers/evm-wallet.tsgaps/GAP_1786322222.mditems 10–11Recommended direction
Stage them on one
/sign/evmchange (do not split MetaMask vs Binance — same page, same error):window.ethereum.providers,window.ethereum,window.BinanceChain. Wait briefly / listen foreip6963:announceProviderandethereum#initializedso late MetaMask iOS / Binance WebView injection is not a false miss. If several providers announce, let the user pick (rdns / name); do not silently sign with a hidden injected wallet.No EVM wallet found (install MetaMask or similar)as the only UI. Show Open in MetaMask, Open in Binance Web3, and Copy link, using documented universal / dapp-browser links whoseurlis the current portal sign page (query string intact). Idle copy should say the wallet app browser can sign; Chrome/Safari cannot use a desktop extension. If a documented Binance dapp-browser URL cannot be confirmed, still ship Copy-link + MetaMask link rather than inventing a scheme.personal_signof the canonical legal UTF-8 message via LegalVITE_WC_PROJECT_ID. Mobile is not QR-only: Open MetaMask / Open Binance Web3 / Copy pairing link (wc:URI), user-gesture<a href>only, scheme allowlist. Hide WC when the project id is unset (same as Galaxy Station). Pairing hrefs must not encode query-suppliedredirect_uri.window.ethereum+ MetaMask Chrome extension must keep working.Do not tell Android/iOS users to install the MetaMask Chrome extension.
Acceptance criteria
/sign/evm?property=…never dead-ends onNo EVM wallet found (install MetaMask or similar)without a working next step (Open in MetaMask and/or Open in Binance Web3 and/or in-page WalletConnect + Copy link).signed_latest: truefor(property, EVM, 0x…)from MetaMask iOS in-app browser (injected or EIP-6963).signed_latest: truefrom Binance Web3 in-app browser (injectedethereum/BinanceChain/ EIP-6963).signed_latest: true.window.ethereumpath still works (existing e2e).account=0x…is on the query string, a different connected address fails closed (nosigned_latestfor the claimed account).skills/portal-sign-disclosure/SKILL.mdandsignShell.tscomments no longer say EVM must omit all extraControls if EVM CTAs ship.Test plan (functional paths)
window.ethereum: existingevm-sign.spec.tshappy path (regression) +redirect.spec.tsallowlist.Error: No EVM wallet found…as the only UI; button re-enables (#10).eip6963:announceProviderwith nowindow.ethereum→ Connect & sign still requests accounts andpersonal_signs the canonical message.window.ethereumappears afterethereum#initialized(or after a short delay) → no false miss if the user waits / retries.window.BinanceChainonly: treated as EIP-1193-compatible whenrequestexists.javascript:and foreign origins rejected; queryredirect_uriis not the deeplink target.https://…/sign/evm?…, not a wallet host.wc:;personal_signof legal message → submit →signed_latest: true. UnsetVITE_WC_PROJECT_IDhides Galaxy-style WC (fail closed, not a thrown Cloud error).getStatusshort-circuit still shows success without a second signature.account=0xaaa…with connected0xbbb…fails closed.request/ WC connect must not run while checkbox is unchecked.Test plan (attack / abuse / hack vectors)
property/version_label/content_sha256/ accountaccount_idaddress Baccount=0x…≠ connected walletredirect_urior a non-portal originjavascript:,data:, unknown schemes in Open-in-app / pairing hrefhttps://evil.example/…as pairing URLisAllowed…equivalent)window.ethereum/ EIP-6963 announce in page (XSS / malicious extension)account_ideth_signof prehash / EIP-712 typed data instead ofpersonal_signof legal UTF-8MAX_REQUEST_BODY_BYTES)Verification criteria
signed_latest: true.cd api && cargo testEVM verify + wallet integration tests remain green.cd web && npm test+ Playwrightevm-sign/sign-pages/ new missing-provider specs green (5 workers).sign_urls.evmno longer strand mobile system-browser users on this error.mentioned in commit
e455f1a9d8mentioned in merge request !32
mentioned in commit
72c45a73d5mentioned in merge request voting!28
marked as related to #16
mentioned in merge request !33