Skip to main content

Signing messages

Immutable Passport supports signing the following message standards:

Sign Messages

Create the Web3Provider by fetching the Passport provider by calling passportInstance.connectEvm() after initialising Passport. Then create a new EtherJS Web3Provider and pass the Passport provider into the constructor.


// fetch the Passport provider from the Passport instance
const passportProvider = passportInstance.connectEvm();

// create the Web3Provider using the Passport provider
const web3Provider = new ethers.providers.Web3Provider(passportProvider);

Once the Web3Provider is created, call eth_requestAccounts to trigger the login flow for Passport if the user is not signed in.

// calling eth_requestAccounts triggers the Passport login flow
const accounts = await web3Provider.provider.request({ method: 'eth_requestAccounts' });

Retrieve the signer from the Web3Provider, set the chainId (zkEVM Testnet) and use our utility function to get a typed payload which contains our domain, message and types (see the utils directory for the full details). Then call 'eth_signTypedData_v4', to trigger the Passport popup for the user to sign the message.

const signMessage = async () => {
// set signed state message to pending in the view
setSignedMessageState('pending signature');

// fetch the signer from the Web3provider
const signer = web3Provider.getSigner();

// set the chainId
const chainId = 13473; // zkEVM testnet

// set the sender address
const address = await signer.getAddress();

// get our message payload - including domain, message and types (see utils/etherMailTypedPayload)
const etherMailTypedPayload = getEtherMailTypedPayload(chainId, address)

setParams([
address,
etherMailTypedPayload
])

try {
// attempt to sign the message, this brings up the passport popup
// if successful update the signed message to successful in the view
const signature = await passportProvider.request({
method: 'eth_signTypedData_v4',
params: [address, etherMailTypedPayload],
})

setSignature(signature)
setSignedMessageState('user successfully signed message');

} catch (error: any) {
// Handle user denying signature
if (error.code === 4001) {
// if the user declined update the signed message to declined in the view
setSignedMessageState('user declined to sign');
} else {
// if something else went wrong, update the generic error with message in the view
setSignedMessageState(`something went wrong - ${error.message}`);
console.log(error);
}
}
};

It's important to note the chainId must match the chainId your passport instance is connected to, in this case it's zkEVM testnet.

You should also handle the errors returned by the Passport popup by wrapping the await in try catch statement and notifying the user if there is an error.

Checkout the full working example here: Passport with NextJS Singing with EIP-712.

Verify Signatures

💡Note
To verify a signature, the users smart contract wallet must have been deployed previously. This occurs when the user submits their first transaction.

Verifying the authenticity of a signature can be done by calling the respective methods which wrap isValidSignature. Check the utils directory for the implementation.

Here is the wrapper code from the example app showing signature verfication for EIP712s.


const isValidTypedDataSignature = async (
address: string, //The Passport wallet address returned from eth_requestAccounts
payload: string, //The stringified payload
signature: string, //The signature
zkEvmProvider: passport.Provider, // can be any provider, Passport or not
) => {
const typedPayload: passport.TypedDataPayload = JSON.parse(payload);
const types = { ...typedPayload.types };
// @ts-ignore
// Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
delete types.EIP712Domain;

//The hashed string
const digest = ethers.utils._TypedDataEncoder.hash(
typedPayload.domain,
types,
typedPayload.message,
);
return isValidSignature(address, digest, signature, zkEvmProvider);
};

const verifySignature = async () => {
setVerifiedStateMessage("Pending Verification");

try {
// validate the signature
const isValid = await isValidTypedDataSignature(
params[0], // the signer address
JSON.stringify(params[1]), // the etherMail payload
signature,
passportProvider,
);

// set verified message state based on validation value
isValid ? setVerifiedStateMessage("Signature verified") : setVerifiedStateMessage("Signature couldn't be verified");

} catch (error: any) {
// if something else went wrong, update the generic error with message in the view
setSignedMessageState(`something went wrong - ${error.message}`);
console.log(error);
}
}