@@ -106,8 +106,11 @@ import type {
106106  JWK , 
107107  JwtPayload , 
108108  JwtHeader , 
109+   SolanaWeb3Credentials , 
110+   SolanaWallet , 
111+   Web3Credentials , 
109112}  from  './lib/types' 
110- import  {  stringToUint8Array  }  from  './lib/base64url' 
113+ import  {  stringToUint8Array ,   bytesToBase64URL  }  from  './lib/base64url' 
111114
112115polyfillGlobalThis ( )  // Make "globalThis" available 
113116
@@ -601,6 +604,214 @@ export default class GoTrueClient {
601604    } ) 
602605  } 
603606
607+   /** 
608+    * Signs in a user by verifying a message signed by the user's private key. 
609+    * Only Solana supported at this time, using the Sign in with Solana standard. 
610+    */ 
611+   async  signInWithWeb3 ( credentials : Web3Credentials ) : Promise < 
612+     |  { 
613+         data : {  session : Session ;  user : User  } 
614+         error : null 
615+       } 
616+     |  {  data : {  session : null ;  user : null  } ;  error : AuthError  } 
617+   >  { 
618+     const  {  chain }  =  credentials 
619+ 
620+     if  ( chain  ===  'solana' )  { 
621+       return  await  this . signInWithSolana ( credentials ) 
622+     } 
623+ 
624+     throw  new  Error ( `@supabase/auth-js: Unsupported chain "${ chain }  ) 
625+   } 
626+ 
627+   private  async  signInWithSolana ( credentials : SolanaWeb3Credentials )  { 
628+     let  message : string 
629+     let  signature : Uint8Array 
630+ 
631+     if  ( 'message'  in  credentials )  { 
632+       message  =  credentials . message 
633+       signature  =  credentials . signature 
634+     }  else  { 
635+       const  {  chain,  wallet,  statement,  options }  =  credentials 
636+ 
637+       let  resolvedWallet : SolanaWallet 
638+ 
639+       if  ( ! isBrowser ( ) )  { 
640+         if  ( typeof  wallet  !==  'object'  ||  ! options ?. url )  { 
641+           throw  new  Error ( 
642+             '@supabase/auth-js: Both wallet and url must be specified in non-browser environments.' 
643+           ) 
644+         } 
645+ 
646+         resolvedWallet  =  wallet 
647+       }  else  if  ( typeof  wallet  ===  'object' )  { 
648+         resolvedWallet  =  wallet 
649+       }  else  { 
650+         const  windowAny  =  window  as  any 
651+ 
652+         if  ( 
653+           'solana'  in  windowAny  && 
654+           typeof  windowAny . solana  ===  'object'  && 
655+           ( ( 'signIn'  in  windowAny . solana  &&  typeof  windowAny . solana . signIn  ===  'function' )  || 
656+             ( 'signMessage'  in  windowAny . solana  && 
657+               typeof  windowAny . solana . signMessage  ===  'function' ) ) 
658+         )  { 
659+           resolvedWallet  =  windowAny . solana 
660+         }  else  { 
661+           throw  new  Error ( 
662+             `@supabase/auth-js: No compatible Solana wallet interface on the window object (window.solana) detected. Make sure the user already has a wallet installed and connected for this app. Prefer passing the wallet interface object directly to signInWithWeb3({ chain: 'solana', wallet: resolvedUserWallet }) instead.` 
663+           ) 
664+         } 
665+       } 
666+ 
667+       const  url  =  new  URL ( options ?. url  ??  window . location . href ) 
668+ 
669+       if  ( 'signIn'  in  resolvedWallet  &&  resolvedWallet . signIn )  { 
670+         const  output  =  await  resolvedWallet . signIn ( { 
671+           issuedAt : new  Date ( ) . toISOString ( ) , 
672+ 
673+           ...options ?. signInWithSolana , 
674+ 
675+           // non-overridable properties 
676+           version : '1' , 
677+           domain : url . host , 
678+           uri : url . href , 
679+ 
680+           ...( statement  ? {  statement }  : null ) , 
681+         } ) 
682+ 
683+         let  outputToProcess : any 
684+ 
685+         if  ( Array . isArray ( output )  &&  output [ 0 ]  &&  typeof  output [ 0 ]  ===  'object' )  { 
686+           outputToProcess  =  output [ 0 ] 
687+         }  else  if  ( 
688+           output  && 
689+           typeof  output  ===  'object'  && 
690+           'signedMessage'  in  output  && 
691+           'signature'  in  output 
692+         )  { 
693+           outputToProcess  =  output 
694+         }  else  { 
695+           throw  new  Error ( '@supabase/auth-js: Wallet method signIn() returned unrecognized value' ) 
696+         } 
697+ 
698+         if  ( 
699+           'signedMessage'  in  outputToProcess  && 
700+           'signature'  in  outputToProcess  && 
701+           ( typeof  outputToProcess . signedMessage  ===  'string'  || 
702+             outputToProcess . signedMessage  instanceof  Uint8Array )  && 
703+           outputToProcess . signature  instanceof  Uint8Array 
704+         )  { 
705+           message  = 
706+             typeof  outputToProcess . signedMessage  ===  'string' 
707+               ? outputToProcess . signedMessage 
708+               : new  TextDecoder ( ) . decode ( outputToProcess . signedMessage ) 
709+           signature  =  outputToProcess . signature 
710+         }  else  { 
711+           throw  new  Error ( 
712+             '@supabase/auth-js: Wallet method signIn() API returned object without signedMessage and signature fields' 
713+           ) 
714+         } 
715+       }  else  { 
716+         if  ( 
717+           ! ( 'signMessage'  in  resolvedWallet )  || 
718+           typeof  resolvedWallet . signMessage  !==  'function'  || 
719+           ! ( 'publicKey'  in  resolvedWallet )  || 
720+           typeof  resolvedWallet  !==  'object'  || 
721+           ! resolvedWallet . publicKey  || 
722+           ! ( 'toBase58'  in  resolvedWallet . publicKey )  || 
723+           typeof  resolvedWallet . publicKey . toBase58  !==  'function' 
724+         )  { 
725+           throw  new  Error ( 
726+             '@supabase/auth-js: Wallet does not have a compatible signMessage() and publicKey.toBase58() API' 
727+           ) 
728+         } 
729+ 
730+         message  =  [ 
731+           `${ url . host }  , 
732+           resolvedWallet . publicKey . toBase58 ( ) , 
733+           ...( statement  ? [ '' ,  statement ,  '' ]  : [ '' ] ) , 
734+           'Version: 1' , 
735+           `URI: ${ url . href }  , 
736+           `Issued At: ${ options ?. signInWithSolana ?. issuedAt  ??  new  Date ( ) . toISOString ( ) }  , 
737+           ...( options ?. signInWithSolana ?. notBefore 
738+             ? [ `Not Before: ${ options . signInWithSolana . notBefore }  ] 
739+             : [ ] ) , 
740+           ...( options ?. signInWithSolana ?. expirationTime 
741+             ? [ `Expiration Time: ${ options . signInWithSolana . expirationTime }  ] 
742+             : [ ] ) , 
743+           ...( options ?. signInWithSolana ?. chainId 
744+             ? [ `Chain ID: ${ options . signInWithSolana . chainId }  ] 
745+             : [ ] ) , 
746+           ...( options ?. signInWithSolana ?. nonce  ? [ `Nonce: ${ options . signInWithSolana . nonce }  ]  : [ ] ) , 
747+           ...( options ?. signInWithSolana ?. requestId 
748+             ? [ `Request ID: ${ options . signInWithSolana . requestId }  ] 
749+             : [ ] ) , 
750+           ...( options ?. signInWithSolana ?. resources ?. length 
751+             ? [ 
752+                 'Resources' , 
753+                 ...options . signInWithSolana . resources . map ( ( resource )  =>  `- ${ resource }  ) , 
754+               ] 
755+             : [ ] ) , 
756+         ] . join ( '\n' ) 
757+ 
758+         const  maybeSignature  =  await  resolvedWallet . signMessage ( 
759+           new  TextEncoder ( ) . encode ( message ) , 
760+           'utf8' 
761+         ) 
762+ 
763+         if  ( ! maybeSignature  ||  ! ( maybeSignature  instanceof  Uint8Array ) )  { 
764+           throw  new  Error ( 
765+             '@supabase/auth-js: Wallet signMessage() API returned an recognized value' 
766+           ) 
767+         } 
768+ 
769+         signature  =  maybeSignature 
770+       } 
771+     } 
772+ 
773+     try  { 
774+       const  {  data,  error }  =  await  _request ( 
775+         this . fetch , 
776+         'POST' , 
777+         `${ this . url }  , 
778+         { 
779+           headers : this . headers , 
780+           body : { 
781+             chain : 'solana' , 
782+             message, 
783+             signature : bytesToBase64URL ( signature ) , 
784+ 
785+             ...( credentials . options ?. captchaToken 
786+               ? {  gotrue_meta_security : {  captcha_token : credentials . options ?. captchaToken  }  } 
787+               : null ) , 
788+           } , 
789+           xform : _sessionResponse , 
790+         } 
791+       ) 
792+       if  ( error )  { 
793+         throw  error 
794+       } 
795+       if  ( ! data  ||  ! data . session  ||  ! data . user )  { 
796+         return  { 
797+           data : {  user : null ,  session : null  } , 
798+           error : new  AuthInvalidTokenResponseError ( ) , 
799+         } 
800+       } 
801+       if  ( data . session )  { 
802+         await  this . _saveSession ( data . session ) 
803+         await  this . _notifyAllSubscribers ( 'SIGNED_IN' ,  data . session ) 
804+       } 
805+       return  {  data : {  ...data  } ,  error } 
806+     }  catch  ( error )  { 
807+       if  ( isAuthError ( error ) )  { 
808+         return  {  data : {  user : null ,  session : null  } ,  error } 
809+       } 
810+ 
811+       throw  error 
812+     } 
813+   } 
814+ 
604815  private  async  _exchangeCodeForSession ( authCode : string ) : Promise < 
605816    |  { 
606817        data : {  session : Session ;  user : User ;  redirectType : string  |  null  } 
0 commit comments