follow-up(#16): bind integrator account= on /sign/solana #17

Closed
opened 2026-08-31 04:31:50 +00:00 by PlasticDigits · 5 comments
PlasticDigits commented 2026-08-31 04:31:50 +00:00 (Migrated from gitlab.com)

Summary

Follow-up to #16 (EVM account= remaining coverage) and the same integrator contract as Terra #11 / EVM #15.

SDK TermsGate / buildSignUrl({ account }) (0.1.1+) puts account= on every network sign URL, including sign_urls.solana. /sign/terra-classic and /sign/evm bind that query and refuse a different connected wallet. /sign/solana ignores it and signs window.solana’s pubkey.

Current codebase

web/src/pages/solana.ts is still a one-file shell:

  1. Requires property; reads app_name / redirect_uri only — does not call getClaimedAccount().
  2. Renders “Connect Phantom or another Solana wallet” + Connect & sign. No Sign as …, no signShell, no terms/consent (#2 postponed).
  3. On click: window.solana or throw No Solana wallet found; connect(); accountId = publicKey.toString(); build canonical message with that id; signMessage(utf8); POST network: "SOLANA".
  4. No getStatus already-signed short-circuit (EVM/Terra have this after connect).
  5. btn.disabled = true only in try; catch re-enables. No shared signShell finally busy reset.
Layer Today
Query web/src/query.ts getClaimedAccount() already trims account for all networks. Solana page never reads it.
SDK packages/cl8y-clickwrap/src/urls.ts appendSignParams / buildSignUrl set account on the Solana URL. TermsGate Accept uses NETWORK_SIGN_URL_KEYS.Solana → solana.
API normalize api/src/account.rs SOLANA: bs58 decode must be 32 bytes; string is stored as submitted (do not lowercase — base58 is case-sensitive).
API verify api/src/verify/solana.rs ed25519 over 0xff || "solana offchain" || LE u64 len || msg. Portal signs raw UTF-8 (signMessage(..., "utf8")). Known P0 envelope mismatch (gaps/GAP_1786322222.md § Incomplete / Security #2).
Tests web/e2e/sign-pages.spec.ts: property + button only (“terms disclosure postponed”). No Solana unit tests, no claimed-account tests, no wallet mock e2e. #4 explicitly excluded Solana e2e.
Reference bind EVM web/src/evm/account.ts / Terra web/src/terra/chain.ts + mismatch e2e.

Why this implementation is needed

Without a portal bind, an integrator (DEX/bridge/voting-class) that connected Phantom A and redirected with account=<A> can still accept terms for Phantom B if the user switches wallets on the portal. The dApp then polls A and stays unsigned, while the portal shows success for B. Terra/EVM already fail closed with This page is for a different wallet. Solana is the remaining clickwrap network that drops that contract.

SDK 0.1.1 already sends account= on Accept for network="Solana". The hole is only the portal page.

Constraints / guardrails

  1. Solana addresses are case-sensitive. Do not copy EVM toLowerCase(). Canonicalize by: trim → bs58 decode → exactly 32 bytes. Compare decoded bytes (or a single canonical bs58 encode of those bytes). Invalid claimed (0x…, terra1…, javascript:, short/high-bit garbage) → fail closed, same retail mismatch copy as EVM/Terra if possible.
  2. account is not a URL. Never pass it to location, <a href>, or success redirect. Sign as … is a text node (el() / textContent). No innerHTML.
  3. Portal bind is UX. API still verifies the submitted account_id + signature. Do not trust query account on POST /api/v1/signatures/wallet.
  4. Do not silently “fix” the off-chain envelope inside this issue unless you are also closing that P0 with API+portal+golden tests. Mismatch tests must fail before submit. Matching full-stack signed_latest e2e will fail today because UTF-8 signMessage ≠ API envelope — see Out of scope / envelope note.
  5. Do not mount EVM MetaMask/Binance/WC or Terra Keplr/ustr-cmm chrome on /sign/solana.
  6. Do not sneak in full #2 terms disclosure / renderSignShell as a drive-by. Optional: show Sign as on the existing card. If you adopt signShell, that is a documented expansion and must keep consent-gate invariants; default recommendation is a thin page change + web/src/solana/account.ts.
  7. Do not add Phantom-mobile deeplinks / wallet-adapter matrix here (EVM #15 / Terra #9/#11 class of work). window.solana only, matching today’s page.
  8. Already-signed: after connect, if getStatus(property, "SOLANA", bound) is signed_latest, show success without forcing another signMessage (parity with EVM/Terra). Fetch status only after connect (no pre-connect probe).
  9. Message bytes stay buildWalletMessage / Content-SHA256 lockstep (#6). Do not invent a Solana-only message format.
  10. Playwright: Chromium, workers = 5, mock window.solana only. No real Phantom.

Relevant files

  1. Add canonicalizeSolanaAddress / assertSolanaAccountContinuity next to EVM’s helper. Reuse mismatch copy: This page is for a different wallet. Reconnect that account and try again.
  2. In renderSolana: claimedAccount = getClaimedAccount(); if set, prepend Sign as <claimed> (text). On connect, bound = assertSolanaAccountContinuity(claimed, publicKey.toString()). Build message / submit with bound only.
  3. After bind, getStatus(property, "SOLANA", bound); if signed_latest, renderSuccess and return.
  4. Unit-test canonicalize (32-byte bs58, reject 0x / terra1 / empty / wrong length) and mismatch / no-claim pass-through / byte-equal encodings if you canonicalize encode.
  5. Playwright: mock window.solana connect + signMessage. Mismatch e2e does not need API verify success. Matching e2e may assert portal did not show mismatch and called signMessage with the bound id; do not require signed_latest until the envelope P0 is fixed (or mock the API). Document which bar you chose in the MR.
  6. Re-enable Connect & sign on mismatch (button currently stays disabled only if you forget catch — keep fail path enabling the button).

Acceptance criteria

  • With account=<valid bs58 32-byte> on /sign/solana?property=…, page shows Sign as <that pubkey> as text.
  • Connected pubkey equals claimed (byte-level) → sign path uses that account id in the canonical message and submit body.
  • Connected pubkey differs → mismatch error, no submitWallet, no Accepted heading, claimed account remains unsigned.
  • Invalid / hostile account → fail closed; not used as href / navigation.
  • No account query → current behavior (sign whatever window.solana returns, then API binds that account_id).
  • Already signed_latest for the bound account → success without a second signature.
  • No EVM/Terra extraControls on this page.
  • API normalize_account / verify_solana unchanged except if envelope work is explicitly in the MR (default: unchanged).
  • Unit tests for the helper; Playwright mismatch (and matching bar as above).
  • cd web && npx vitest run src/solana src/query.test.ts (and page tests if added) green; Playwright workers=5 for any new spec.

Test plan (functional paths)

  1. No claim: mock Phantom, Connect & sign → message accountId is mock pubkey (existing submit path).
  2. Match: query account= = mock pubkey → Sign as visible → sign uses that id.
  3. Mismatch: query other 32-byte bs58 → different wallet → no POST / no Accepted.
  4. Already signed + match: mock getSignatureStatus / API already true → success, signMessage not required.
  5. Missing window.solana: existing error; CTA usable again.
  6. Missing property: existing missing-property page (unchanged).
  7. app_name / redirect_uri: still work; success redirect still allowlisted (web/src/redirect.ts). account must not replace redirect_uri.

Test plan (attack / abuse)

  1. account=javascript:… / data: / https://evil: not an href; bind fail-closed; Sign as text-only if rendered.
  2. account=0x + 40 hex or terra1…: invalid Solana pubkey → mismatch, no submit.
  3. Case tweak: if two bs58 strings decode to the same 32 bytes, treat as match; if they decode to different bytes, mismatch. Never lowercase “to be safe.”
  4. Query strip: user removes account= → can sign another wallet and see portal success; integrator polling the original pubkey stays unsigned. Do not authenticate the query param.
  5. Query swap: user sets account= to a key they control → they may accept for that key; dApp still polls its store. Document; no server ACL on the query.
  6. Envelope confusion: do not accept an ed25519 sig over raw UTF-8 as if it were the off-chain envelope (or vice versa) to “make e2e green.” That is the separate P0.
  7. XSS in claimed pubkey string → text node only.
  8. Do not treat a UI checkbox as Solana authentication (there is no consent gate on this page today).

Verification criteria

cd web && npx vitest run src/solana src/query.test.ts
# if e2e added:
cd web && npx playwright test --workers=5 e2e/sign-pages.spec.ts   # plus any solana-sign.spec.ts
  • Mismatch case is visible in the Playwright report.
  • Manual: /sign/solana?property=<registered>&account=<phantom A> with wallet B connected must not Accepted.

Out of scope

  • #16 EVM matching/WC/deeplink proof.
  • #2 full terms + consent on Solana (renderSignShell).
  • #4 historical “no Solana e2e” for EVM/Terra coverage — this issue may add Solana tests for account bind only.
  • Solana sign/verify envelope alignment (portal UTF-8 vs API solana offchain prefix) — still a P0 in the gap doc; do not bury it as a side effect unless the MR is explicitly dual-scoped.
  • Phantom in-app browser / mobile deeplink / multi-wallet adapter.
  • Telegram account= (numeric user ids, not Solana pubkeys).
  • Changing EIP-191 / ADR-036 paths.
## Summary Follow-up to [#16](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/16) (EVM `account=` remaining coverage) and the same integrator contract as Terra [#11](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/11) / EVM [#15](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/15). SDK `TermsGate` / `buildSignUrl({ account })` (0.1.1+) puts `account=` on **every** network sign URL, including `sign_urls.solana`. `/sign/terra-classic` and `/sign/evm` bind that query and refuse a different connected wallet. **`/sign/solana` ignores it** and signs `window.solana`’s pubkey. ## Current codebase `web/src/pages/solana.ts` is still a one-file shell: 1. Requires `property`; reads `app_name` / `redirect_uri` only — **does not call** `getClaimedAccount()`. 2. Renders “Connect Phantom or another Solana wallet” + **Connect & sign**. No *Sign as …*, no `signShell`, no terms/consent (#2 postponed). 3. On click: `window.solana` or throw `No Solana wallet found`; `connect()`; `accountId = publicKey.toString()`; build canonical message with that id; `signMessage(utf8)`; POST `network: "SOLANA"`. 4. No `getStatus` already-signed short-circuit (EVM/Terra have this after connect). 5. `btn.disabled = true` only in `try`; catch re-enables. No shared `signShell` `finally` busy reset. | Layer | Today | |--------|--------| | Query | [`web/src/query.ts`](web/src/query.ts) `getClaimedAccount()` already trims `account` for all networks. Solana page never reads it. | | SDK | [`packages/cl8y-clickwrap/src/urls.ts`](packages/cl8y-clickwrap/src/urls.ts) `appendSignParams` / `buildSignUrl` set `account` on the Solana URL. [`TermsGate`](packages/cl8y-clickwrap/src/react/TermsGate.tsx) Accept uses `NETWORK_SIGN_URL_KEYS.Solana` → `solana`. | | API normalize | [`api/src/account.rs`](api/src/account.rs) `SOLANA`: bs58 decode must be **32 bytes**; string is stored as submitted (**do not lowercase** — base58 is case-sensitive). | | API verify | [`api/src/verify/solana.rs`](api/src/verify/solana.rs) ed25519 over **`0xff \|\| "solana offchain" \|\| LE u64 len \|\| msg`**. Portal signs **raw UTF-8** (`signMessage(..., "utf8")`). Known P0 envelope mismatch ([`gaps/GAP_1786322222.md`](gaps/GAP_1786322222.md) § Incomplete / Security #2). | | Tests | [`web/e2e/sign-pages.spec.ts`](web/e2e/sign-pages.spec.ts): property + button only (“terms disclosure postponed”). **No** Solana unit tests, **no** claimed-account tests, **no** wallet mock e2e. `#4` explicitly excluded Solana e2e. | | Reference bind | EVM [`web/src/evm/account.ts`](web/src/evm/account.ts) / Terra [`web/src/terra/chain.ts`](web/src/terra/chain.ts) + mismatch e2e. | ## Why this implementation is needed Without a portal bind, an integrator (DEX/bridge/voting-class) that connected Phantom **A** and redirected with `account=<A>` can still accept terms for Phantom **B** if the user switches wallets on the portal. The dApp then polls **A** and stays unsigned, while the portal shows success for **B**. Terra/EVM already fail closed with *This page is for a different wallet.* Solana is the remaining clickwrap network that drops that contract. SDK 0.1.1 already sends `account=` on Accept for `network="Solana"`. The hole is only the portal page. ## Constraints / guardrails 1. **Solana addresses are case-sensitive.** Do **not** copy EVM `toLowerCase()`. Canonicalize by: trim → bs58 decode → exactly 32 bytes. Compare **decoded bytes** (or a single canonical bs58 encode of those bytes). Invalid claimed (`0x…`, `terra1…`, `javascript:`, short/high-bit garbage) → fail closed, same retail mismatch copy as EVM/Terra if possible. 2. **`account` is not a URL.** Never pass it to `location`, `<a href>`, or success redirect. *Sign as …* is a **text node** (`el()` / `textContent`). No `innerHTML`. 3. **Portal bind is UX.** API still verifies the submitted `account_id` + signature. Do not trust query `account` on `POST /api/v1/signatures/wallet`. 4. **Do not silently “fix” the off-chain envelope** inside this issue unless you are also closing that P0 with API+portal+golden tests. Mismatch tests must fail **before** submit. Matching **full-stack** `signed_latest` e2e will fail today because UTF-8 `signMessage` ≠ API envelope — see Out of scope / envelope note. 5. **Do not** mount EVM MetaMask/Binance/WC or Terra Keplr/ustr-cmm chrome on `/sign/solana`. 6. **Do not** sneak in full `#2` terms disclosure / `renderSignShell` as a drive-by. Optional: show *Sign as* on the existing card. If you adopt `signShell`, that is a **documented** expansion and must keep consent-gate invariants; default recommendation is a thin page change + `web/src/solana/account.ts`. 7. **Do not** add Phantom-mobile deeplinks / wallet-adapter matrix here (EVM `#15` / Terra `#9`/`#11` class of work). `window.solana` only, matching today’s page. 8. Already-signed: after connect, if `getStatus(property, "SOLANA", bound)` is `signed_latest`, show success without forcing another `signMessage` (parity with EVM/Terra). Fetch status only **after** connect (no pre-connect probe). 9. Message bytes stay `buildWalletMessage` / `Content-SHA256` lockstep (#6). Do not invent a Solana-only message format. 10. Playwright: Chromium, **workers = 5**, mock `window.solana` only. No real Phantom. ## Relevant files - [`web/src/pages/solana.ts`](web/src/pages/solana.ts) (implementation) - [`web/src/query.ts`](web/src/query.ts) `getClaimedAccount` - New: `web/src/solana/account.ts` + `account.test.ts` (mirror EVM helper) - [`web/src/ui.ts`](web/src/ui.ts) `el` / `renderSuccess` - [`web/src/api.ts`](web/src/api.ts) `getStatus` / `submitWallet` - [`web/src/base58.ts`](web/src/base58.ts) (signature encode; pubkey decode should use a decode that matches API `bs58`) - [`api/src/account.rs`](api/src/account.rs) `SOLANA` arm - [`api/src/verify/solana.rs`](api/src/verify/solana.rs) (do not weaken) - [`packages/cl8y-clickwrap/src/urls.ts`](packages/cl8y-clickwrap/src/urls.ts) / [`TermsGate.tsx`](packages/cl8y-clickwrap/src/react/TermsGate.tsx) (already passes `account`) - [`web/e2e/sign-pages.spec.ts`](web/e2e/sign-pages.spec.ts) - [`skills/portal-sign-disclosure/SKILL.md`](skills/portal-sign-disclosure/SKILL.md) (out of scope unless signShell) - [`gaps/GAP_1786322222.md`](gaps/GAP_1786322222.md) Solana envelope + already-signed rows ## Recommended direction 1. Add `canonicalizeSolanaAddress` / `assertSolanaAccountContinuity` next to EVM’s helper. Reuse mismatch copy: *This page is for a different wallet. Reconnect that account and try again.* 2. In `renderSolana`: `claimedAccount = getClaimedAccount()`; if set, prepend *Sign as &lt;claimed&gt;* (text). On connect, `bound = assertSolanaAccountContinuity(claimed, publicKey.toString())`. Build message / submit with `bound` only. 3. After bind, `getStatus(property, "SOLANA", bound)`; if `signed_latest`, `renderSuccess` and return. 4. Unit-test canonicalize (32-byte bs58, reject `0x` / `terra1` / empty / wrong length) and mismatch / no-claim pass-through / byte-equal encodings if you canonicalize encode. 5. Playwright: mock `window.solana` connect + `signMessage`. **Mismatch** e2e does not need API verify success. **Matching** e2e may assert portal did not show mismatch and called `signMessage` with the bound id; do **not** require `signed_latest` until the envelope P0 is fixed (or mock the API). Document which bar you chose in the MR. 6. Re-enable Connect & sign on mismatch (button currently stays disabled only if you forget `catch` — keep fail path enabling the button). ## Acceptance criteria - [ ] With `account=<valid bs58 32-byte>` on `/sign/solana?property=…`, page shows *Sign as &lt;that pubkey&gt;* as text. - [ ] Connected pubkey **equals** claimed (byte-level) → sign path uses that account id in the canonical message and submit body. - [ ] Connected pubkey **differs** → mismatch error, no `submitWallet`, no Accepted heading, claimed account remains unsigned. - [ ] Invalid / hostile `account` → fail closed; not used as `href` / navigation. - [ ] No `account` query → current behavior (sign whatever `window.solana` returns, then API binds that `account_id`). - [ ] Already `signed_latest` for the bound account → success without a second signature. - [ ] No EVM/Terra extraControls on this page. - [ ] API `normalize_account` / `verify_solana` unchanged except if envelope work is explicitly in the MR (default: unchanged). - [ ] Unit tests for the helper; Playwright mismatch (and matching bar as above). - [ ] `cd web && npx vitest run src/solana src/query.test.ts` (and page tests if added) green; Playwright workers=5 for any new spec. ## Test plan (functional paths) 1. **No claim:** mock Phantom, Connect & sign → message `accountId` is mock pubkey (existing submit path). 2. **Match:** query `account=` = mock pubkey → *Sign as* visible → sign uses that id. 3. **Mismatch:** query other 32-byte bs58 → *different wallet* → no POST / no Accepted. 4. **Already signed + match:** mock `getSignatureStatus` / API already true → success, `signMessage` not required. 5. **Missing `window.solana`:** existing error; CTA usable again. 6. **Missing `property`:** existing missing-property page (unchanged). 7. **`app_name` / `redirect_uri`:** still work; success redirect still allowlisted (`web/src/redirect.ts`). `account` must not replace `redirect_uri`. ## Test plan (attack / abuse) 1. **`account=javascript:…` / `data:` / `https://evil`:** not an href; bind fail-closed; *Sign as* text-only if rendered. 2. **`account=0x` + 40 hex or `terra1…`:** invalid Solana pubkey → mismatch, no submit. 3. **Case tweak:** if two bs58 strings decode to the same 32 bytes, treat as match; if they decode to different bytes, mismatch. Never lowercase “to be safe.” 4. **Query strip:** user removes `account=` → can sign another wallet and see portal success; integrator polling the original pubkey stays unsigned. Do not authenticate the query param. 5. **Query swap:** user sets `account=` to a key they control → they may accept for that key; dApp still polls its store. Document; no server ACL on the query. 6. **Envelope confusion:** do not accept an ed25519 sig over raw UTF-8 as if it were the off-chain envelope (or vice versa) to “make e2e green.” That is the separate P0. 7. **XSS** in claimed pubkey string → text node only. 8. **Do not** treat a UI checkbox as Solana authentication (there is no consent gate on this page today). ## Verification criteria ```bash cd web && npx vitest run src/solana src/query.test.ts # if e2e added: cd web && npx playwright test --workers=5 e2e/sign-pages.spec.ts # plus any solana-sign.spec.ts ``` - Mismatch case is visible in the Playwright report. - Manual: `/sign/solana?property=<registered>&account=<phantom A>` with wallet **B** connected must not Accepted. ## Out of scope - [#16](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/16) EVM matching/WC/deeplink proof. - [#2](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/2) full terms + consent on Solana (`renderSignShell`). - [#4](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/issues/4) historical “no Solana e2e” for EVM/Terra coverage — this issue **may** add Solana tests for **account bind only**. - Solana **sign/verify envelope alignment** (portal UTF-8 vs API `solana offchain` prefix) — still a P0 in the gap doc; do not bury it as a side effect unless the MR is explicitly dual-scoped. - Phantom in-app browser / mobile deeplink / multi-wallet adapter. - Telegram `account=` (numeric user ids, not Solana pubkeys). - Changing EIP-191 / ADR-036 paths.
PlasticDigits commented 2026-08-31 04:31:51 +00:00 (Migrated from gitlab.com)

marked as related to #16

marked as related to #16
PlasticDigits commented 2026-08-31 04:31:52 +00:00 (Migrated from gitlab.com)

marked as related to #2

marked as related to #2
PlasticDigits commented 2026-08-31 04:31:53 +00:00 (Migrated from gitlab.com)

marked as related to #4

marked as related to #4
PlasticDigits commented 2026-08-31 09:22:28 +00:00 (Migrated from gitlab.com)

mentioned in commit 4f2904960c

mentioned in commit 4f2904960cb7215274eb0b517fb214348ad4dd55
PlasticDigits commented 2026-08-31 09:23:18 +00:00 (Migrated from gitlab.com)

Verify — 2026-08-31 (4f29049 on main)

Implemented portal account= bind on /sign/solana (GitLab #17). Envelope P0 (UTF-8 signMessage vs API solana offchain) is unchanged — matching e2e does not require signed_latest.

What landed

  • web/src/solana/account.ts — trim → bs58 decode → 32 bytes; compare decoded bytes; never lowercase
  • web/src/pages/solana.ts — Sign as text node; bind before sign/submit; getStatus only after connect; already-signed skips signMessage; button re-enables on mismatch/missing wallet
  • web/src/base58.ts base58ToUint8 lockstep with API bs58
  • Playwright web/e2e/solana-sign.spec.ts (workers=5, mock window.solana)
  • Skill: skills/solana-account-bind/SKILL.md

API normalize_account / verify_solana not modified.

Verification checklist (issue body)

  • Valid account= shows Sign as <pubkey> as a text node
  • Matching connected pubkey → canonical message + submit account_id use that id
  • Mismatch → different wallet, no submitWallet, no Accepted, claimed stays unsigned
  • Hostile javascript: / terra1 / 0x → fail closed; not an href; button re-enables
  • No account query → signs mock window.solana pubkey
  • Already signed_latest (mocked status) → success without signMessage
  • No EVM/Terra extraControls on this page
  • API verify/normalize unchanged
  • cd web && npx vitest run src/solana src/query.test.ts green (plus src/base58.test.ts)
  • cd web && npx playwright test --workers=5 e2e/solana-sign.spec.ts e2e/sign-pages.spec.ts green
  • Full Playwright matrix 43/43 workers=5 (EVM/Terra/redirect unchanged)

Matching bar chosen: assert signMessage + POST account_id, do not require Accepted / signed_latest (envelope P0).

All #17 criteria pass. Closing.

## Verify — 2026-08-31 (`4f29049` on `main`) Implemented portal `account=` bind on `/sign/solana` (GitLab #17). Envelope P0 (UTF-8 `signMessage` vs API `solana offchain`) is **unchanged** — matching e2e does not require `signed_latest`. ### What landed - `web/src/solana/account.ts` — trim → bs58 decode → 32 bytes; compare decoded bytes; never lowercase - `web/src/pages/solana.ts` — *Sign as* text node; bind before sign/submit; `getStatus` only after connect; already-signed skips `signMessage`; button re-enables on mismatch/missing wallet - `web/src/base58.ts` `base58ToUint8` lockstep with API `bs58` - Playwright `web/e2e/solana-sign.spec.ts` (workers=5, mock `window.solana`) - Skill: [`skills/solana-account-bind/SKILL.md`](https://gitlab.com/plasticdigits/cl8y-ecosystem-legal/-/blob/main/skills/solana-account-bind/SKILL.md) API `normalize_account` / `verify_solana` not modified. ### Verification checklist (issue body) - [x] Valid `account=` shows *Sign as &lt;pubkey&gt;* as a text node - [x] Matching connected pubkey → canonical message + submit `account_id` use that id - [x] Mismatch → *different wallet*, no `submitWallet`, no Accepted, claimed stays unsigned - [x] Hostile `javascript:` / `terra1` / `0x` → fail closed; not an `href`; button re-enables - [x] No `account` query → signs mock `window.solana` pubkey - [x] Already `signed_latest` (mocked status) → success without `signMessage` - [x] No EVM/Terra extraControls on this page - [x] API verify/normalize unchanged - [x] `cd web && npx vitest run src/solana src/query.test.ts` green (plus `src/base58.test.ts`) - [x] `cd web && npx playwright test --workers=5 e2e/solana-sign.spec.ts e2e/sign-pages.spec.ts` green - [x] Full Playwright matrix 43/43 workers=5 (EVM/Terra/redirect unchanged) Matching bar chosen: assert `signMessage` + POST `account_id`, **do not** require Accepted / `signed_latest` (envelope P0). All #17 criteria pass. Closing.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-31 09:23:19 +00:00
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-ecosystem-legal#17
No description provided.