For users who already have wallets, Privy supports signing in with Ethereum (SIWE) or Solana (SIWS). With this flow, users who are already onchain can bring their existing wallet to your app, verify ownership of assets, and take onchain actions.
To have users login to your app with a wallet, use the login
method from the usePrivy
hook.
Login with wallet is only available using Privy UIs.
login : ({ loginMethods ?: [' wallet '], walletChainType ?: ' ethereum - only ' | ' solana - only ' | ' ethereum - and - solana ', disableSignup ?: boolean }) => void ;
Parameters The login methods to enable.
walletChainType
'ethereum-only' | 'solana-only' | 'ethereum-and-solana'
The chain type of the wallet to login with.
Whether to disable signup for the login method.
Returns This method does not return anything.
Usage import { useLogin , usePrivy } from '@privy-io/react-auth' ;
function LoginButton () {
const { ready , authenticated } = usePrivy ();
const { login } = useLogin ();
// Disable login when Privy is not ready or the user is already authenticated
const disableLogin = ! ready || ( ready && authenticated );
return (
< button
disabled = { disableLogin }
onClick = { () => login ({
loginMethods: [ 'wallet' ],
walletChainType: 'ethereum-and-solana' ,
disableSignup: false
}) }
>
Log in
</ button >
);
}
Sign in with Ledger on Solana Currently, Ledger Solana hardware wallets only support transaction signatures, not the message signatures required
for Sign-In With Solana (SIWS) authentication. In order to authenticate with a Solana Ledger wallet,
you will need to mount the following hook in your application:
import { useSolanaLedgerPlugin } from '@privy-io/react-auth/solana' ;
...
// Ensure this is mounted throughout the entire sign in flow
useSolanaLedgerPlugin ();
Then, when you attempt to login with a Phantom Solana wallet, you will be prompted to indicate whether you are signing with a Ledger wallet,
which will initiate a separate SIWS flow wherein which a no-op transaction will be signed and used for verification.
To have users login to your app with a wallet, use the login
method from the usePrivy
hook.
Login with wallet is only available using Privy UIs.
login : ({ loginMethods ?: [' wallet '], walletChainType ?: ' ethereum - only ' | ' solana - only ' | ' ethereum - and - solana ', disableSignup ?: boolean }) => void ;
Parameters The login methods to enable.
walletChainType
'ethereum-only' | 'solana-only' | 'ethereum-and-solana'
The chain type of the wallet to login with.
Whether to disable signup for the login method.
Returns This method does not return anything.
Usage import { useLogin , usePrivy } from '@privy-io/react-auth' ;
function LoginButton () {
const { ready , authenticated } = usePrivy ();
const { login } = useLogin ();
// Disable login when Privy is not ready or the user is already authenticated
const disableLogin = ! ready || ( ready && authenticated );
return (
< button
disabled = { disableLogin }
onClick = { () => login ({
loginMethods: [ 'wallet' ],
walletChainType: 'ethereum-and-solana' ,
disableSignup: false
}) }
>
Log in
</ button >
);
}
Sign in with Ledger on Solana Currently, Ledger Solana hardware wallets only support transaction signatures, not the message signatures required
for Sign-In With Solana (SIWS) authentication. In order to authenticate with a Solana Ledger wallet,
you will need to mount the following hook in your application:
import { useSolanaLedgerPlugin } from '@privy-io/react-auth/solana' ;
...
// Ensure this is mounted throughout the entire sign in flow
useSolanaLedgerPlugin ();
Then, when you attempt to login with a Phantom Solana wallet, you will be prompted to indicate whether you are signing with a Ledger wallet,
which will initiate a separate SIWS flow wherein which a no-op transaction will be signed and used for verification.
Ethereum (SIWE) Solana (SIWS) To authenticate a user via an Ethereum wallet (SIWE ) , use the React Native SDK’s useLoginWithSiwe
hook.
In order to use Privy’s login with wallet flow, users must actively have an ethereum wallet connected to your app from which you can request signatures.
Generate SIWE message generateSiweMessage ({ wallet: { chainId: string , address: string }, from: { domain: string , uri: string }}) => Promise < string >
Parameters Wallet object containing EIP-55 compliant wallet address and chainId in CAIP-2 format.
The chain ID of the wallet.
The address of the wallet.
Origin object containing domain and uri.
The domain of the origin.
Returns A SIWE message that can be signed by the wallet.
Usage import { useLoginWithSiwe } from '@privy-io/expo' ;
export function LoginScreen () {
const [ address , setAddress ] = useState ( '' );
const [ message , setMessage ] = useState ( '' );
const { generateSiweMessage } = useLoginWithSiwe ();
const handleGenerate = async () => {
const message = await generateSiweMessage ({
from: {
domain: 'my-domain.com' ,
uri: 'https://my-domain.com' ,
},
wallet: {
// sepolia chainId with CAIP-2 prefix
chainId: `eip155:11155111` ,
address ,
},
});
setMessage ( message );
};
return (
< View >
< TextInput
value = { address }
onChangeText = { setAddress }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button onPress = { handleGenerate } > Generate Message </ Button >
{ Boolean ( message ) && < Text > { message } </ Text > }
</ View >
);
}
Then, request an EIP-191 personal_sign
signature for the message
returned by generateSiweMessage
, from a connected wallet.
There are many ways to connect a wallet to a mobile app, a few good options are:
Login with SIWE loginWithSiwe ({ signature: string , messageOverride? : string , disableSignup? : boolean }) => Promise < Result < PrivyUser >>
Parameters The signature of the SIWE message, signed by the user’s wallet.
An optional override for the message that is signed.
If true, the user will not be automatically created if they do not exist in the Privy database.
Returns A PrivyUser object containing the user’s information.
Usage import { useLoginWithSiwe , usePrivy } from '@privy-io/expo' ;
export function LoginScreen () {
const [ signature , setSignature ] = useState ( '' );
const { user } = usePrivy ();
const { loginWithSiwe } = useLoginWithSiwe ();
if ( user ) {
return (
<>
< Text > Logged In </ Text >
< Text > { JSON . stringify ( user , null , 2 ) } </ Text >
</>
);
}
return (
< View >
< TextInput
value = { signature }
onChangeText = { setSignature }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button onPress = { () => loginWithSiwe ({ signature }) } > Login </ Button >
</ View >
);
}
Callbacks You can optionally pass callbacks into the useLoginWithSiwe
hook to run custom logic after a message has been generated, after a successful login, or to handle errors that occur during the flow.
onGenerateMessage
onGenerateMessage ?: (( message : string ) => void ) | undefined
Parameters The SIWE message that was generated.
onSuccess
onSuccess ?: (( user : PrivyUser , isNewUser : boolean ) => void ) | undefined
Parameters The user object corresponding to the authenticated user.
Whether the user is a new user or an existing user.
onError
onError ?: ( error : Error ) => void
Parameters The error that occurred during the login flow.
Usage import { useLoginWithSiwe } from '@privy-io/expo' ;
export function LoginScreen () {
const { generateSiweMessage , loginWithSiwe } = useLoginWithSiwe ({
onGenerateMessage ( message ) {
// show a toast, send analytics event, etc...
},
onSuccess ( user , isNewUser ) {
// show a toast, send analytics event, etc...
},
onError ( error ) {
// show a toast, update form errors, etc...
},
});
// ...
}
Tracking login flow state The state
variable returned from useLoginWithSiwe
will always be one of the following values.
type SiweFlowState =
| { status : "initial" }
| { status : "error" ; error : Error | null }
| { status : "generating-message" }
| { status : "awaiting-signature" }
| { status : "submitting-signature" }
| { status : "done" };
To authenticate a user via an Ethereum wallet (SIWE ) , use the React Native SDK’s useLoginWithSiwe
hook.
In order to use Privy’s login with wallet flow, users must actively have an ethereum wallet connected to your app from which you can request signatures.
Generate SIWE message generateSiweMessage ({ wallet: { chainId: string , address: string }, from: { domain: string , uri: string }}) => Promise < string >
Parameters Wallet object containing EIP-55 compliant wallet address and chainId in CAIP-2 format.
The chain ID of the wallet.
The address of the wallet.
Origin object containing domain and uri.
The domain of the origin.
Returns A SIWE message that can be signed by the wallet.
Usage import { useLoginWithSiwe } from '@privy-io/expo' ;
export function LoginScreen () {
const [ address , setAddress ] = useState ( '' );
const [ message , setMessage ] = useState ( '' );
const { generateSiweMessage } = useLoginWithSiwe ();
const handleGenerate = async () => {
const message = await generateSiweMessage ({
from: {
domain: 'my-domain.com' ,
uri: 'https://my-domain.com' ,
},
wallet: {
// sepolia chainId with CAIP-2 prefix
chainId: `eip155:11155111` ,
address ,
},
});
setMessage ( message );
};
return (
< View >
< TextInput
value = { address }
onChangeText = { setAddress }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button onPress = { handleGenerate } > Generate Message </ Button >
{ Boolean ( message ) && < Text > { message } </ Text > }
</ View >
);
}
Then, request an EIP-191 personal_sign
signature for the message
returned by generateSiweMessage
, from a connected wallet.
There are many ways to connect a wallet to a mobile app, a few good options are:
Login with SIWE loginWithSiwe ({ signature: string , messageOverride? : string , disableSignup? : boolean }) => Promise < Result < PrivyUser >>
Parameters The signature of the SIWE message, signed by the user’s wallet.
An optional override for the message that is signed.
If true, the user will not be automatically created if they do not exist in the Privy database.
Returns A PrivyUser object containing the user’s information.
Usage import { useLoginWithSiwe , usePrivy } from '@privy-io/expo' ;
export function LoginScreen () {
const [ signature , setSignature ] = useState ( '' );
const { user } = usePrivy ();
const { loginWithSiwe } = useLoginWithSiwe ();
if ( user ) {
return (
<>
< Text > Logged In </ Text >
< Text > { JSON . stringify ( user , null , 2 ) } </ Text >
</>
);
}
return (
< View >
< TextInput
value = { signature }
onChangeText = { setSignature }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button onPress = { () => loginWithSiwe ({ signature }) } > Login </ Button >
</ View >
);
}
Callbacks You can optionally pass callbacks into the useLoginWithSiwe
hook to run custom logic after a message has been generated, after a successful login, or to handle errors that occur during the flow.
onGenerateMessage
onGenerateMessage ?: (( message : string ) => void ) | undefined
Parameters The SIWE message that was generated.
onSuccess
onSuccess ?: (( user : PrivyUser , isNewUser : boolean ) => void ) | undefined
Parameters The user object corresponding to the authenticated user.
Whether the user is a new user or an existing user.
onError
onError ?: ( error : Error ) => void
Parameters The error that occurred during the login flow.
Usage import { useLoginWithSiwe } from '@privy-io/expo' ;
export function LoginScreen () {
const { generateSiweMessage , loginWithSiwe } = useLoginWithSiwe ({
onGenerateMessage ( message ) {
// show a toast, send analytics event, etc...
},
onSuccess ( user , isNewUser ) {
// show a toast, send analytics event, etc...
},
onError ( error ) {
// show a toast, update form errors, etc...
},
});
// ...
}
Tracking login flow state The state
variable returned from useLoginWithSiwe
will always be one of the following values.
type SiweFlowState =
| { status : "initial" }
| { status : "error" ; error : Error | null }
| { status : "generating-message" }
| { status : "awaiting-signature" }
| { status : "submitting-signature" }
| { status : "done" };
To authenticate a user via a Solana wallet (SIWS ) , use the React Native SDK’s useLoginWithSiws
hook.
In order to use Privy’s login with wallet flow, users must actively have a Solana wallet connected to your app from which you can request signatures.
Generate SIWS message generateMessage ({ wallet: { address: string }, from: { domain: string , uri: string }}) => Promise < { message : string } >
Parameters Wallet object containing Solana wallet address.
The address of the wallet.
Origin object containing domain and uri.
The domain of the origin.
Returns A SIWS message that can be signed by the wallet.
Usage import { useLoginWithSiws } from '@privy-io/expo' ;
export function LoginScreen () {
const [ address , setAddress ] = useState ( '' );
const [ message , setMessage ] = useState ( '' );
const { generateMessage } = useLoginWithSiws ();
const handleGenerate = async () => {
const { message } = await generateMessage ({
from: {
domain: 'my-domain.com' ,
uri: 'https://my-domain.com' ,
},
wallet: {
address ,
},
});
setMessage ( message );
};
return (
< View >
< TextInput
value = { address }
onChangeText = { setAddress }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button onPress = { handleGenerate } > Generate Message </ Button >
{ Boolean ( message ) && < Text > { message } </ Text > }
</ View >
);
}
Sign the SIWS message Then, request a signature for the message
returned by generateMessage
, from a connected wallet.
Login with SIWS login ({ signature: string , message: string , wallet: { walletClientType: string , connectorType: string }, disableSignup? : boolean }) => Promise < Result < PrivyUser >>
Parameters The signature of the SIWS message, signed by the user’s wallet.
The original message that was signed.
The client of the connected wallet (e.g. ‘phantom’).
The type of the connector (e.g. ‘wallet_connect’ or ‘mobile_wallet_protocol’).
If true, the user will not be automatically created if they do not exist in the Privy database.
Returns A PrivyUser object containing the user’s information.
Usage import { useLoginWithSiws , usePrivy } from '@privy-io/expo' ;
export function LoginScreen () {
const [ signature , setSignature ] = useState ( '' );
const { user } = usePrivy ();
const { login } = useLoginWithSiws ();
if ( user ) {
return (
<>
< Text > Logged In </ Text >
< Text > { JSON . stringify ( user , null , 2 ) } </ Text >
</>
);
}
return (
< View >
< TextInput
value = { signature }
onChangeText = { setSignature }
placeholder = "0x..."
inputMode = "ascii-capable"
/>
< Button
onPress = { () =>
login ({
signature ,
message ,
wallet: {
walletClientType ,
connectorType ,
},
})
}
>
Login
</ Button >
</ View >
);
}
To authenticate a user via an Ethereum wallet (SIWE ) , use the Privy client’s siwe
handler.
Generate SIWE message func generateSiweMessage ( params : SiweMessageParams) async throws -> String
Parameters Set of parameters required to generate the message.
Your app’s domain. e.g. “my-domain.com”
EVM Chain ID, e.g. “1” for Ethereum Mainnet
The user’s ERC-55 compliant wallet address.
Returns A SIWE message that can be signed by the wallet.
Usage do {
let params = SiweMessageParams (
appDomain : "my-domain.com" ,
appUri : "https://my-domain.com" ,
chainId : "1" ,
walletAddress : "0x12345..."
)
let siweMessage = try await privy. siwe . generateSiweMessage ( params : params)
} catch {
// An error can be thrown if the network call to generate the message fails,
// or if invalid metadata was passed in.
}
Sign the SIWE message Using the message returned by generateSiweMessage
, request an EIP-191 personal_sign
signature from the user’s connected wallet. You should do this using the library your app uses to connect to external wallets (e.g. the MetaMask iOS SDK or WalletConnect).
Once the user successfully signs the message, store the signature in a variable.
Login with SIWE func loginWithSiwe (
message : String ,
signature : String ,
params : SiweMessageParams,
metadata : WalletLoginMetadata ?
) async throws -> PrivyUser
Parameters The message returned from “generateSiweMessage”.
The signature of the SIWE message, signed by the user’s wallet.
The same SiweMessageParams passed into “generateSiweMessage”.
(Optional) you can pass additional metadata that will be stored with the linked wallet.
An enum specifying the type of wallet used to login. e.g. WalletClientType.metamask
A string identifying how wallet was connected. e.g. “wallet_connect”
Returns The authenticated Privy user
Throws An error if logging the user in is unsuccessful.
Usage do {
let params = SiweMessageParams (
appDomain : "my-domain.com" ,
appUri : "https://my-domain.com" ,
chainId : "1" ,
walletAddress : "0x12345..."
)
// Generate SIWE message
let siweMessage = try await privy. siwe . generateSiweMessage ( params : siweParams)
// Optional metadata
let metadata = WalletLoginMetadata (
walletClientType : WalletClientType. metamask ,
connectorType : "wallet_connect"
)
// Login
try await privy. siwe . loginWithSiwe (
message : siweMessage,
// the signature generated by the user's wallet
signature : signature,
params : siweParams,
metadata : metadata
)
} catch {
// error logging user in
}
To authenticate a user via an Ethereum wallet (SIWE ) , use the Privy client’s siwe
handler.
Generate SIWE message func generateSiweMessage ( params : SiweMessageParams) async throws -> String
Parameters Set of parameters required to generate the message.
Your app’s domain. e.g. “my-domain.com”
EVM Chain ID, e.g. “1” for Ethereum Mainnet
The user’s ERC-55 compliant wallet address.
Returns A SIWE message that can be signed by the wallet.
Usage do {
let params = SiweMessageParams (
appDomain : "my-domain.com" ,
appUri : "https://my-domain.com" ,
chainId : "1" ,
walletAddress : "0x12345..."
)
let siweMessage = try await privy. siwe . generateSiweMessage ( params : params)
} catch {
// An error can be thrown if the network call to generate the message fails,
// or if invalid metadata was passed in.
}
Sign the SIWE message Using the message returned by generateSiweMessage
, request an EIP-191 personal_sign
signature from the user’s connected wallet. You should do this using the library your app uses to connect to external wallets (e.g. the MetaMask iOS SDK or WalletConnect).
Once the user successfully signs the message, store the signature in a variable.
Login with SIWE func loginWithSiwe (
message : String ,
signature : String ,
params : SiweMessageParams,
metadata : WalletLoginMetadata ?
) async throws -> PrivyUser
Parameters The message returned from “generateSiweMessage”.
The signature of the SIWE message, signed by the user’s wallet.
The same SiweMessageParams passed into “generateSiweMessage”.
(Optional) you can pass additional metadata that will be stored with the linked wallet.
An enum specifying the type of wallet used to login. e.g. WalletClientType.metamask
A string identifying how wallet was connected. e.g. “wallet_connect”
Returns The authenticated Privy user
Throws An error if logging the user in is unsuccessful.
Usage do {
let params = SiweMessageParams (
appDomain : "my-domain.com" ,
appUri : "https://my-domain.com" ,
chainId : "1" ,
walletAddress : "0x12345..."
)
// Generate SIWE message
let siweMessage = try await privy. siwe . generateSiweMessage ( params : siweParams)
// Optional metadata
let metadata = WalletLoginMetadata (
walletClientType : WalletClientType. metamask ,
connectorType : "wallet_connect"
)
// Login
try await privy. siwe . loginWithSiwe (
message : siweMessage,
// the signature generated by the user's wallet
signature : signature,
params : siweParams,
metadata : metadata
)
} catch {
// error logging user in
}