@@ -18,6 +18,7 @@ import { printHuman, isJSONMode, printJSON } from "../lib/output.js";
1818import { CLIError , ErrorCode , errAuthRequired , errInvalidArgs , errWalletKeyRequired , exitWithError } from "../lib/errors.js" ;
1919import { green , dim , printKeyValueBox } from "../lib/ui.js" ;
2020import { createPendingSession , loadSession , clearSession , isSessionValid , updateSession } from "../lib/wallet-session.js" ;
21+ import * as walletSession from "../lib/wallet-session.js" ;
2122
2223type WalletType = "evm" | "solana" ;
2324const CONNECT_POLL_INTERVAL_MS = 2_000 ;
@@ -97,6 +98,85 @@ async function getSolanaWalletAddress(secretKey: string): Promise<string> {
9798const WALLET_KEYS_DIR = "wallet-keys" ;
9899const UUID_SLICE_LEN = 8 ;
99100const ADDRESS_SLICE_LEN = 12 ;
101+ type WalletStatus = "active" | "expired" | "none" ;
102+ type RemoteWalletStatus = Awaited < ReturnType < typeof getRemoteWalletSession > > [ "status" ] ;
103+
104+ function normalizeRemoteStatusForStorage ( status : RemoteWalletStatus ) : "pending" | "approved" | "revoked" | "expired" {
105+ if ( status === "denied" ) {
106+ return "revoked" ;
107+ }
108+ return status ;
109+ }
110+
111+ function isFutureIsoDate ( value : string ) : boolean {
112+ const date = new Date ( value ) ;
113+ if ( Number . isNaN ( date . getTime ( ) ) ) {
114+ return false ;
115+ }
116+ return date > new Date ( ) ;
117+ }
118+
119+ function isActiveWalletSession (
120+ session : walletSession . WalletSession | null ,
121+ remoteStatus ?: RemoteWalletStatus ,
122+ ) : boolean {
123+ if ( ! session ) return false ;
124+ if ( session . status !== "approved" ) return false ;
125+ if ( ! isFutureIsoDate ( session . expiresAt ) ) return false ;
126+ if ( remoteStatus && remoteStatus !== "approved" ) return false ;
127+ return true ;
128+ }
129+
130+ function deriveWalletStatus (
131+ session : walletSession . WalletSession | null ,
132+ remoteStatus ?: RemoteWalletStatus ,
133+ ) : WalletStatus {
134+ if ( ! session ) return "none" ;
135+ if ( isActiveWalletSession ( session , remoteStatus ) ) return "active" ;
136+ if ( session . status === "pending" ) return "none" ;
137+ return "expired" ;
138+ }
139+
140+ function resolveSessionAddress ( session : walletSession . WalletSession | null ) : string | null {
141+ if ( ! session ) return null ;
142+ return session . evmAddress ?? session . solanaAddress ?? null ;
143+ }
144+
145+ function resolveSessionEnvironment ( session : walletSession . WalletSession | null ) : string | null {
146+ if ( ! session ) return null ;
147+ if ( session . backendBaseUrl ) {
148+ try {
149+ const { hostname } = new URL ( session . backendBaseUrl ) ;
150+ return hostname ;
151+ } catch {
152+ return session . backendBaseUrl ;
153+ }
154+ }
155+
156+ if ( session . environment ) {
157+ return session . environment . interactive ? "interactive" : "non-interactive" ;
158+ }
159+
160+ return null ;
161+ }
162+
163+ function resolveEnabledCapabilities (
164+ session : walletSession . WalletSession | null ,
165+ ) : string [ ] {
166+ if ( ! session ?. capabilities ) {
167+ return [ ] ;
168+ }
169+
170+ return Object . entries ( session . capabilities )
171+ . filter ( ( [ , value ] ) => value )
172+ . map ( ( [ key ] ) => key )
173+ . sort ( ( left , right ) => left . localeCompare ( right ) ) ;
174+ }
175+
176+ function formatWalletStatus ( value : WalletStatus ) : string {
177+ if ( value === "active" ) return green ( "active" ) ;
178+ return dim ( value ) ;
179+ }
100180
101181function walletKeysDirPath ( ) : string {
102182 return join ( config . configDir ( ) , WALLET_KEYS_DIR ) ;
@@ -402,23 +482,25 @@ export function registerWallet(program: Command) {
402482 }
403483
404484 const session = createPendingSession ( ) ;
485+ const sessionEnvironment = {
486+ platform : process . platform ,
487+ arch : process . arch ,
488+ nodeVersion : process . version ,
489+ interactive : isInteractiveAllowed ( program ) ,
490+ } ;
405491 updateSession ( {
406492 chainType : "evm" ,
407493 capabilities : { ...DEFAULT_WALLET_CAPABILITIES } ,
408494 backendBaseUrl : getWalletApiBaseUrl ( ) ,
495+ environment : sessionEnvironment ,
409496 } ) ;
410497
411498 const remoteSession = await createRemoteWalletSession ( authToken , {
412499 publicKeyJwk : session . publicKeyJwk ,
413500 requestSignerVersion : session . envelopeVersion ,
414501 chainType : "evm" ,
415502 capabilities : { ...DEFAULT_WALLET_CAPABILITIES } ,
416- environment : {
417- platform : process . platform ,
418- arch : process . arch ,
419- nodeVersion : process . version ,
420- interactive : isInteractiveAllowed ( program ) ,
421- } ,
503+ environment : sessionEnvironment ,
422504 } ) . catch ( ( err ) => {
423505 clearSession ( ) ;
424506 throw err ;
@@ -497,6 +579,7 @@ export function registerWallet(program: Command) {
497579 chainType : approvedSession . chainType ?? "evm" ,
498580 capabilities : approvedSession . capabilities ?? { ...DEFAULT_WALLET_CAPABILITIES } ,
499581 backendBaseUrl : getWalletApiBaseUrl ( ) ,
582+ environment : sessionEnvironment ,
500583 } ) ;
501584
502585 if ( isJSONMode ( ) ) {
@@ -528,59 +611,87 @@ export function registerWallet(program: Command) {
528611
529612 cmd
530613 . command ( "status" )
531- . description ( "Show current delegated wallet session status" )
532- . action ( ( ) => {
614+ . option ( "--verify" , "Verify delegated session status with backend" )
615+ . description ( "Show delegated wallet session status and metadata" )
616+ . action ( async ( opts : { verify ?: boolean } ) => {
533617 try {
534- const session = loadSession ( ) ;
535- const walletId = session ?. walletId ?? session ?. evmWalletId ;
536-
537- if ( ! session ) {
538- printHuman (
539- ` ${ dim ( "No wallet session. Run" ) } alchemy wallet connect ${ dim ( "to get started." ) } \n` ,
540- { connected : false } ,
541- ) ;
542- return ;
618+ let session = walletSession . loadStoredSession ?.( ) ?? loadSession ( ) ;
619+ let remoteStatus : RemoteWalletStatus | null = null ;
620+
621+ if ( opts . verify && session ) {
622+ const authToken = resolveAuthToken ( ) ;
623+ if ( ! authToken ) throw errAuthRequired ( ) ;
624+
625+ const remote = await getRemoteWalletSession ( authToken , session . sessionId ) ;
626+ remoteStatus = remote . status ;
627+ const walletId = remote . walletId ?? remote . evmWalletId ;
628+
629+ session = {
630+ ...session ,
631+ status : normalizeRemoteStatusForStorage ( remote . status ) ,
632+ expiresAt : remote . expiresAt ?? session . expiresAt ,
633+ privyAppId : remote . privyAppId ?? session . privyAppId ,
634+ walletId : walletId ?? session . walletId ,
635+ evmWalletId : remote . evmWalletId ?? walletId ?? session . evmWalletId ,
636+ evmAddress : remote . evmAddress ?? remote . address ?? session . evmAddress ,
637+ solanaWalletId : remote . solanaWalletId ?? session . solanaWalletId ,
638+ solanaAddress : remote . solanaAddress ?? session . solanaAddress ,
639+ chainType : remote . chainType ?? session . chainType ,
640+ capabilities : remote . capabilities ?? session . capabilities ,
641+ } ;
642+ walletSession . saveSession ( session ) ;
543643 }
544644
645+ const status = deriveWalletStatus ( session , remoteStatus ?? undefined ) ;
646+ const walletAddress = resolveSessionAddress ( session ) ;
647+ const sessionEnvironment = resolveSessionEnvironment ( session ) ;
648+ const signerCapabilities = resolveEnabledCapabilities ( session ) ;
649+ const walletId = session ?. walletId ?? session ?. evmWalletId ?? null ;
650+ const expiresAt = session ?. expiresAt ?? null ;
651+
545652 if ( isJSONMode ( ) ) {
546653 printJSON ( {
547- sessionId : session . sessionId ,
548- status : session . status ,
549- evmAddress : session . evmAddress ?? null ,
550- walletId : walletId ?? null ,
551- createdAt : session . createdAt ,
552- expiresAt : session . expiresAt ,
553- valid : isSessionValid ( session ) ,
554- chainType : session . chainType ?? null ,
555- capabilities : session . capabilities ?? null ,
654+ walletAddress,
655+ status,
656+ expiresAt,
657+ environment : sessionEnvironment ,
658+ signerCapabilities,
659+ sessionId : session ?. sessionId ?? null ,
660+ sessionState : session ?. status ?? null ,
661+ walletId,
662+ chainType : session ?. chainType ?? null ,
663+ verified : Boolean ( opts . verify ) ,
664+ remoteStatus,
665+ valid : session ? isSessionValid ( session ) : false ,
556666 } ) ;
557667 return ;
558668 }
559669
560670 const pairs : [ string , string ] [ ] = [
561- [ "Session ID" , session . sessionId ] ,
562- [ "Status" , session . status ] ,
671+ [ "Wallet Address" , walletAddress ?? dim ( "none" ) ] ,
672+ [ "Session Status" , formatWalletStatus ( status ) ] ,
673+ [ "Session Expiry" , expiresAt ?? dim ( "none" ) ] ,
674+ [ "Environment" , sessionEnvironment ?? dim ( "none" ) ] ,
675+ [ "Signer Capabilities" , signerCapabilities . length > 0 ? signerCapabilities . join ( ", " ) : dim ( "none" ) ] ,
563676 ] ;
564- if ( session . evmAddress ) pairs . push ( [ "EVM Address" , session . evmAddress ] ) ;
565- if ( walletId ) pairs . push ( [ "Wallet ID" , walletId ] ) ;
566- if ( session . chainType ) pairs . push ( [ "Chain Type" , session . chainType ] ) ;
567- pairs . push ( [ "Created" , session . createdAt ] ) ;
568- pairs . push ( [ "Expires" , session . expiresAt ] ) ;
569-
570- if ( session . capabilities ) {
571- const caps = Object . entries ( session . capabilities )
572- . filter ( ( [ , v ] ) => v )
573- . map ( ( [ k ] ) => k )
574- . join ( ", " ) ;
575- if ( caps ) pairs . push ( [ "Capabilities" , caps ] ) ;
677+ if ( session ?. sessionId ) {
678+ pairs . push ( [ "Session ID" , session . sessionId ] ) ;
679+ }
680+ if ( walletId ) {
681+ pairs . push ( [ "Wallet ID" , walletId ] ) ;
682+ }
683+ if ( opts . verify ) {
684+ pairs . push ( [ "Backend Status" , remoteStatus ?? dim ( "not checked" ) ] ) ;
576685 }
577686
578687 printKeyValueBox ( pairs ) ;
579688
580- if ( session . status === "approved " ) {
689+ if ( status === "active " ) {
581690 console . log ( ` ${ green ( "✓" ) } Wallet session active` ) ;
691+ } else if ( status === "none" ) {
692+ console . log ( ` ${ dim ( "No delegated wallet session found. Run" ) } alchemy wallet connect ${ dim ( "to get started." ) } ` ) ;
582693 } else {
583- console . log ( ` ${ dim ( "Session is " ) } ${ session . status } ` ) ;
694+ console . log ( ` ${ dim ( "Wallet session is not active. Run " ) } alchemy wallet connect --force ${ dim ( "to reconnect." ) } ` ) ;
584695 }
585696 } catch ( err ) {
586697 exitWithError ( err ) ;
0 commit comments