SessionKit docs

Scopes

What you can ask for, what the user sees, and what can never be granted.

A scope is one thing you're asking the user to share. You request them in connect(); the wallet shows each one in plain language and the user approves or declines. Nothing outside the granted set is ever sent.

The catalog

ScopeYou receiveThe user sees
identity user.id, user.pubKey, user.proof "Sign you in"
profile:username user.username "See your username"
profile:avatar user.avatar, user.avatarMimeType "See your profile picture"
address:solana user.solanaAddress, with a possession proof "See your Solana address"
address:evm user.evmAddress, with a possession proof "See your Ethereum address"
favorites:read user.favoriteMarkets "See your favorite markets"
sign:solana session.request for Solana messages and transactions, plus address:solana "Ask you to sign Solana transactions"
sign:evm session.request for EVM messages and transactions, plus address:evm "Ask you to sign Ethereum & Polygon transactions"

identity is added automatically — every session returns a verifiable user, that's the point of signing in. Each sign:* scope brings its address scope with it. The rest you ask for explicitly.

Signing scopes

A sign:* scope keeps the encrypted channel open after approval so your dapp can propose signatures. The wallet shows each proposal decoded, with its BLACWALL risk verdict and your verified origin, and the user approves every one of them. The wallet fills nonce, gas and fees itself, and refuses blind hash signing and contract creation.

sign:evm covers transactions on Ethereum and Polygon. Messages and typed data also work on Arbitrum One (eip155:42161).

What can never be granted

There is no trading scope, and there never will be. A SessionKit session can never act on the user's BLAC trading account: it cannot place, modify, or cancel orders, and cannot change leverage or margin. The wallet enforces this by audience, not by who you are — so it holds even if your key leaks, even if your site is compromised.

Without a sign:* scope, the worst case of a breach on your side is the data the user explicitly agreed to share. With one, an attacker can still only propose: nothing is signed without the user's approval in the wallet. Requesting a scope outside the catalog throws invalid_scope before anything is rendered.

Ask for less

Every extra scope is a reason to decline. Approval rates fall as the list grows, and unused data is a liability you have to store, secure, and delete.

  • Just signing people in? Request nothing — identity alone is a complete login.
  • Showing who they are? Add profile:username and profile:avatar.
  • Checking NFTs or token holdings? Add address:solana or address:evm.
  • Running a dapp that needs signatures? Add sign:solana or sign:evm, and nothing more.

Handling partial grants

A user can approve the login and decline an optional scope. Check grantedScopes, and degrade rather than fail:

const session = await connect({
  scopes: ["identity", "profile:username", "address:solana"],
  mount, sign
});

if (session.grantedScopes.includes("address:solana")) {
  await loadNfts(session.user.solanaAddress);
} else {
  showConnectWalletLater();   // they're still logged in
}

Adding chains

New scopes are added additively, with no protocol version bump. To request one you update SessionKit, because connect() throws invalid_scope for any scope this version does not know. A scope the user's wallet does not support yet simply comes back ungranted, so check grantedScopes from day one.