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, setup the domain, types and message. Then call signer._signTypedData(domain, types, message) 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();

// Define our "domain separator" to ensure user signatures are unique across apps/chains
const domain = {
name: 'Ether Mail',
version: '1',
chainId,
verifyingContract: address,
};

// setup the types for displaying the message in the signing window
const types = {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
};

// setup the message to be signed
const message = {
from: {
name: 'Cow',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
};

try {
// attempt to sign the message, this brings up the passport popup
await signer._signTypedData(domain, types, message);

// if successful update the signed message to successful in the view
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}`);
}
}
};

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 isSignatureValid method on the user's smart contract.

import { ethers, providers } from 'ethers';

// https://eips.ethereum.org/EIPS/eip-1271#specification
// EIP-1271 states that `isValidSignature` must return the following value if the signature is valid
const ERC_1271_MAGIC_VALUE = '0x1626ba7e';

export const isSignatureValid = async (
address: string, // The Passport wallet address returned from eth_requestAccounts
payload: any,
signature: string,
zkEvmProvider: providers.Provider // can be any provider, Passport or not
) => {
const types = { ...payload.types };
// Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
delete types.EIP712Domain;

const hash = ethers.utils._TypedDataEncoder.hash(
payload.domain,
types,
payload.message
);
const contract = new ethers.Contract(
address,
['function isValidSignature(bytes32, bytes) public view returns (bytes4)'],
zkEvmProvider
);

const isValidSignatureHex = await contract.isValidSignature(hash, signature);
return isValidSignatureHex === ERC_1271_MAGIC_VALUE;
};