Workspace IndexDev Notes › Connect once means authorize a session

#37PoC

Connect once means authorize a session

A WalletConnect connection is not merely an address exchange. Its namespaces, chains, methods, accounts, expiry, and update events describe a standing capability that the application must track.

Create one minimal and one broad session proposal with the WalletConnect Sign client (or Reown AppKit), then exercise account change, chain change, session update, expiry, relay disconnect, reconnect, and explicit revocation — asserting each event narrows or invalidates application state correctly.

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.

← All Dev Notes · Workspace Index · Top ↑

한 번 연결한다는 것은 세션을 인가한다는 뜻이다

WalletConnect 연결은 단순한 주소 교환이 아닙니다. namespace, 체인, 메서드, 계정, 만료, 갱신 이벤트가 애플리케이션이 추적해야 할 상시 권한을 기술합니다.

WalletConnect Sign client(또는 Reown AppKit)로 최소 권한 제안과 넓은 권한 제안을 하나씩 만들고, 계정 변경, 체인 변경, 세션 갱신, 만료, 릴레이 단절, 재연결, 명시적 해지를 차례로 실행합니다 — 각 이벤트가 애플리케이션 상태를 올바르게 좁히거나 무효화하는지 확인합니다.

UI 는 보통 세션 상태를 "connected" 하나로 뭉갭니다. 그러면 어떤 메서드가 아직 인가되어 있는지가 가려지고, 지갑이 애플리케이션 바깥에서 상태를 바꿀 때 낡은 계정 버그가 생깁니다.

WalletConnect 세션에는 계정, 체인, 메서드, 이벤트, 만료가 들어 있습니다. 이것을 isConnected 불리언 하나로 다루면, 애플리케이션이 무엇을 요청할 수 있는지와 캐시된 계정이 아직 최신인지 알려 주는 정보가 사라집니다.

동작 방식

가장 작은 메서드 집합으로 연결하고 넓은 기본 제안과 비교한 뒤, 계정 변경, 체인 변경, 세션 갱신, 만료, 릴레이 단절, 명시적 해지를 차례로 실행합니다.

PoC

WalletConnect Sign client 또는 Reown AppKit 으로 최소 권한 제안과 넓은 권한 제안을 각각 만듭니다. 계정·체인 변경, 세션 갱신, 만료, 릴레이 단절, 재연결, 명시적 연결 해제를 실행합니다. 각 이벤트가 애플리케이션 상태를 올바르게 무효화하거나 좁히는지 확인합니다.

무엇을 증명하나

연결 UX 는 권한 관리입니다. 안전한 기본값은 최소한의 namespace 와 메서드 집합, 관찰 가능한 세션 상태 머신, 눈에 보이는 해지 경로입니다 — 낡은 상태에서도 살아남는 초록색 점이 아닙니다.

참고: Reown WalletConnect 세션 사용법.

검토 후 보완

같은 병, 다른 장기 — 생애주기에서 인가로

제목이 곧 재정의입니다 — 한 번 연결 = 세션을 인가하는 것이지 주소를 교환하는 게 아니다. 세션은 상시 권한 — 범위 + 수명 + 변경 이벤트 — 이고, 불리언 isConnected 는 그걸 거의 다 버리는 손실 있는 투영입니다. a-boolean-hides-a-state-machine 이 이름 붙인 패턴의 인가 쪽 멤버입니다.

초록 점은 세 개의 독립 계층을 뭉갠다

private-rpc-visibility·bundler-paymaster-dependencies 의 바로 그 구조적 오류가 지갑에 나타난 것입니다:

계층 진짜 질문 초록 점의 위장
릴레이/전송 지금 지갑과 대화할 수 있나? "연결됨"
세션/인가 그 권한이 아직 유효한가(만료·취소 아님)? "연결됨"
계정/상태 캐시된 계정·체인이 아직 최신인가? "연결됨"

릴레이 끊김은 세션을 무효화하지 않고, 재연결이 재인가하지 않습니다. 전송 가용성은 인가가 아닙니다 — bundler-paymaster-dependencies 가 "accepted ≠ settled" 로 그은 그 선.

보안의 핵심은 최소권한

세션은 namespaces·chains·methods·accounts(EIP-155 / CAIP-2 / CAIP-10)를 지정합니다. 광범위한 기본 — 모든 체인, 모든 메서드, eth_sign 포함 — 을 요청하는 것은 OAuth 의 "이 앱이 전부에 접근하려 함" 안티패턴의 web3 판입니다. 메서드 집합이 곧 공격 표면입니다 — eth_sign(블라인드 서명)을 인가한 세션은 personal_sign 으로 좁힌 세션보다 범주적으로 큰 부채입니다. 그래서 안전한 기본값은 가장 작은 네임스페이스·메서드 집합이고, 최소 대 광범위 제안 비교가 그 차이를 가정이 아니라 측정 가능하게 만듭니다. embedded-wallet-policy·agentic-intent-veto 와 같은 "실제로 무엇을 인가했나" 질문, 전송 표준 한 층 아래.

스테일 계정이 구체적이고 위험한 버그

지갑은 앱 밖에서 상태를 바꿀 수 있습니다 — 사용자가 계정 전환, 체인 전환, 세션 만료, 취소. UI 가 초록 점 뒤에 캐시된 계정을 쥐고 있으면 틀린 계정·체인의 잔고를 보여주거나 서명을 요청합니다. accountsChanged/chainChanged/session_update/session_delete/session_expire 이벤트는 바로 앱 상태를 좁히거나 무효화하려 존재하고, 이를 무시하는 것이 스테일 상태를 살아남는 초록 점입니다. 한 당사자의 캐시 뷰가 그라운드 트루스가 아니라는 rpc-view-not-consensus 의 교훈을, 당신 자신의 세션 캐시에 적용한 것.

불변식: 모든 이벤트는 좁히거나 무효화하지, 조용히 넓히지 않는다

"각 이벤트가 앱 상태를 올바르게 좁히거나 무효화하는지 단언"이 실제로 인코딩하는 게 이것입니다. 권한은 오직 줄어들거나 새 승인을 요구해야지, 조용히 늘어나면 안 됩니다. 명시적 재제안 없이 메서드나 계정을 얻은 듯 보이는 세션은 버그 — 또는 공격입니다. 세션을 명시적 상태기계(제안됨 → 성립됨 → 갱신됨 → 만료/끊김/취소됨 → 재제안)로 모델링하는 것이 그 불변식을 검사 가능하게 합니다.

만료와 취소는 1급 개념이지 예외가 아니다

expiry 지난 세션은 권한이 아닙니다 — 연결됨으로 취급하는 것은 receipt-is-not-settlement 의 인가판 쌍둥이(완료처럼 보이지만 유효하지 않음)입니다. 그리고 취소는 보이고 양방향이어야 합니다 — 사용자는 지갑에서 session_delete 할 수 있고, 앱도 자체 취소 경로를 드러내야 합니다. 깨끗이 관찰·취소할 수 없는 "연결됨"은 남아 있는 권한 — 잊힌 OAuth 승인 실패 유형이며, 인가 도메인의 who-holds-the-mint / "누가 멈추나" 주제입니다.

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑