import { SignInWithWalletErrorType, SignInWithWalletMessage, } from "@mezo-org/sign-in-with-wallet" import { ONE_DAY_MS } from "./time" const SESSION_EXPIRATION_DURATION_MS = 14 * ONE_DAY_MS type NetworkFamily = "evm" | "bitcoin" export function createSignInWithWalletMessage( address: string, nonce: string, networkFamily: NetworkFamily, chainId?: number, ) { const { host: domain, origin: uri } = window.location let siwwMessage try { siwwMessage = new SignInWithWalletMessage({ domain, address, // if "bitcoin" this should be btc address, not underlaying eth address uri, nonce, issuedAt: new Date().toISOString(), expirationTime: new Date( Date.now() + SESSION_EXPIRATION_DURATION_MS, ).toISOString(), version: "1", chainId: networkFamily === "evm" ? chainId : undefined, networkFamily, }) } catch (error) { throw new Error(`Failed to create sign in with wallet message: ${error}`) } return siwwMessage.prepareMessage() } class SIWWDomainError extends Error { constructor(message: string) { super(`Error when verifying domain in SIWW message: ${message}`) this.name = "SIWWDomainError" } } export async function verifyDomainInSignInWithWalletMessage( message: string, signature: string, nonce: string, ) { const { host: domain } = window.location let siwwMessage: SignInWithWalletMessage try { siwwMessage = new SignInWithWalletMessage(message) } catch (error) { throw new SIWWDomainError("Failed to parse SIWW message") } if (!siwwMessage.expirationTime) { throw new SIWWDomainError("SIWW messages must have an expiration time set") } const result = await siwwMessage.verify( { signature, // Nonce has to match the session ID carried in the request. nonce, domain, // Time is used as a reference to verify the expiration time set in the // message. time: new Date().toISOString(), }, { suppressExceptions: true }, ) if ( result.error && result.error.type === SignInWithWalletErrorType.DOMAIN_MISMATCH ) { throw new SIWWDomainError( "Domain does not match provided domain for verification.", ) } }