Why
The UI usually collapses session state to "connected." That hides which methods remain authorized and produces stale-account bugs when the wallet changes state outside the application.
A WalletConnect session contains accounts, chains, methods, events, and expiry. Treating it as a boolean isConnected discards the information that says what the application may request and whether its cached account is still current.
How it works
Connect with the smallest method set, compare it with a broad default proposal, then exercise account change, chain change, session update, expiry, relay disconnect, and explicit revocation.
PoC
Create one minimal proposal and one broad proposal using the WalletConnect Sign client or Reown AppKit. Exercise account and chain changes, a session update, expiry, relay loss, reconnect, and explicit disconnect. Assert that each event invalidates or narrows application state correctly.
What it proves
Connection UX is capability management. The safe default is the smallest requested namespace and method set, an observable session state machine, and a visible revocation path — not a green dot that survives stale state.
Reference: Reown WalletConnect session usage.
Review clarification
Same disease, different organ — from lifecycle to authorization
The title is the whole reframe: connect once = authorize a session, not exchange an address. A session is a standing capability — scope + lifetime + mutation events — and a boolean isConnected is a lossy projection that throws almost all of it away. It is the authorization-side member of the pattern named in a-boolean-hides-a-state-machine.
The green dot conflates three independent layers
This is the exact structural error from private-rpc-visibility and bundler-paymaster-dependencies, now in the wallet:
| Layer | The real question | What the green dot pretends |
|---|---|---|
| Relay / transport | can I currently talk to the wallet? | "connected" |
| Session / authorization | is the grant still valid (not expired/revoked)? | "connected" |
| Account / state | is my cached account/chain still current? | "connected" |
A relay disconnect does not invalidate the session; a reconnect does not re-authorize. Transport availability is not authorization — the same line bundler-paymaster-dependencies draws for "accepted ≠ settled."
The security core is least privilege
A session names its namespaces, chains, methods, and accounts (EIP-155 / CAIP-2 / CAIP-10). Requesting a broad default — every chain, every method, eth_sign included — is the OAuth "this app wants access to everything" antipattern landing in web3. The method set is the attack surface: a session authorized for eth_sign (blind signing) is a categorically bigger liability than one scoped to personal_sign. So the safe default is the smallest requested namespace and method set, and the minimal-vs-broad proposal comparison is what makes that difference measurable rather than assumed. Same "what did you actually authorize" question as embedded-wallet-policy and agentic-intent-veto, one layer down at the transport standard.
Stale-account is the concrete, dangerous bug
The wallet can change state outside your app — the user switches account, switches chain, lets the session expire, or revokes. If the UI holds a cached account behind a green dot, it will display balances for, or request a signature from, the wrong account or the wrong chain. The accountsChanged / chainChanged / session_update / session_delete / session_expire events exist precisely to narrow or invalidate app state; ignoring them is the green dot that survives stale state. That is rpc-view-not-consensus's lesson — one party's cached view is not ground truth — applied to your own session cache.
The invariant: every event narrows or invalidates, never silently widens
That is what "assert each event narrows or invalidates application state correctly" really encodes. Authority should only shrink or require a fresh grant; it must never quietly grow. A session that appears to gain a method or an account without an explicit re-proposal is a bug — or an attack. Modeling the session as an explicit state machine (proposed → established → updated → expired/disconnected/revoked → re-proposed) is what makes the invariant checkable.
Expiry and revocation are first-class, not edge cases
A session past expiry is not authority — treating it as connected is the authorization-domain twin of receipt-is-not-settlement (a thing that looks done but is not valid). And revocation must be visible and bidirectional: the user can session_delete from the wallet, and the app must surface its own revocation path. A "connected" you cannot cleanly observe or revoke is a lingering capability — the forgotten-OAuth-grant failure mode, which is the who-holds-the-mint / "who can halt" theme in the authorization domain.
Where it lands in Jayverse
- Wallet: model every connection as an explicit session state machine, not a boolean. Track namespaces, chains, methods, accounts and expiry directly, and request the smallest method set by default — no eth_sign unless a flow specifically needs it.
- Rabbit: apply the same narrow-or-invalidate invariant to EIP-7702/7715 session keys and mandates. Every mutation event should only shrink authority or force a fresh grant; test explicitly that no event silently widens what a session key can do.
- Wallet: make expiry and revocation first-class, visible UI, not edge cases. Surface a session's remaining scope and give the user an explicit revoke action, so a stale-account bug can't hide behind a green "connected" dot.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| standing capability | 지속되는(상시) 권한·능력 · 세션이 부여하는 권한 전체를 가리킬 때. "a standing capability that the application must track" |
| stale-account bug | 오래된(갱신 안 된) 계정 정보로 인한 버그 · 지갑 상태가 바뀐 걸 앱이 못 따라갈 때. "produces stale-account bugs when the wallet changes state" |
| collapse X to Y | X를 Y로 뭉뚱그려버리다(정보를 단순화) · 복잡한 상태를 boolean 하나로 줄일 때. "The UI usually collapses session state to connected" |
| least privilege | 최소 권한 원칙(보안 설계 원칙) · 필요한 만큼만 권한을 부여하는 설계. "The security core is least privilege" |
| blind signing | 내용을 못 보고 서명하는 것(위험한 서명 방식) · 서명 대상이 뭔지 모른 채 승인. "a session authorized for eth_sign (blind signing)" |
| categorically bigger | 차원이 다르게 더 큰 · 위험·책임의 크기를 강조할 때. "a categorically bigger liability than one scoped to" |
| safe default | 안전한 기본값(설정) · 처음부터 위험을 줄이는 기본 옵션. "the safe default is the smallest requested namespace" |
| first-class, not edge cases | 예외가 아니라 핵심으로 다뤄야 할 사항 · 설계에서 반드시 고려해야 할 요소. "Expiry and revocation are first-class, not edge cases" |
| ground truth | 실제 정답·근거가 되는 사실 · 캐시된 값과 실제 상태를 구분할 때. "one party's cached view is not ground truth" |
| lingering capability | 계속 남아있는(방치된) 권한 · 회수되지 않고 남아있는 위험한 접근 권한. "is a lingering capability — the forgotten-OAuth-grant failure mode" |
| WalletConnect | 지갑-앱 연결 프로토콜(월렛커넥트) · 세션 기반으로 권한을 부여하는, 이 카드가 다루는 프로토콜. "A WalletConnect connection is not merely an address exchange" |
| Reown AppKit | WalletConnect 진영이 리브랜딩한 지갑 연결 SDK · 세션 생성에 쓰는 클라이언트 라이브러리. "the WalletConnect Sign client (or Reown AppKit)" |
| CAIP | 체인 애그노스틱 개선안(Chain Agnostic Improvement Proposals) · 계정·체인을 표준 표기로 식별하는 규격. "EIP-155 / CAIP-2 / CAIP-10" |