Message Signing

This guide explains how to sign SIWA (Sign-In with Algorand) messages using different wallet providers: Pera, Defly, Kibisis, and Lute. Each wallet has its own method for signing messages.

Pera Wallet

Pera Wallet supports signing arbitrary data directly:

const signMessageWithPera = async (message: string, address: string): Promise<{ signature: Uint8Array; transaction: null }> => {
  const hashedMessage = hashMessage(message);
  const encodedHashedMessage = getMessageBytes(Buffer.from(hashedMessage).toString("utf8"));

  const peraSigArray = await peraWallet.signData(
    [{ data: encodedHashedMessage, message: "" }],
    address
  );
  return {
    signature: peraSigArray[0],
    transaction: null,
  };
};

Defly Wallet

Defly Wallet requires creating a transaction with the message in the note field:

const signMessageWithDefly = async (message: string, address: string): Promise<{ signature: Uint8Array; transaction: Uint8Array }> => {
  const hashedMessage = hashMessage(message);
  const encodedHashedMessage = getMessageBytes(Buffer.from(hashedMessage).toString("utf8"));

  const suggestedParams = await algodClient.getTransactionParams().do();
  const deflyTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
    note: encodedHashedMessage,
    from: address,
    to: address,
    amount: 0,
    suggestedParams,
  } as any);

  const deflyTxnGroup = [{ txn: deflyTxn, signerAddress: [address] }];
  const deflySigArray = await deflyWallet.signTransaction([deflyTxnGroup]);
  const decodedDeflyTxn = algosdk.decodeSignedTransaction(deflySigArray[0]);
  return {
    signature: decodedDeflyTxn.sig as unknown as Uint8Array,
    transaction: deflySigArray[0],
  };
};

Kibisis Wallet

Kibisis Wallet uses a browser-based approach for signing:

const signMessageWithKibisis = async (message: string): Promise<{ signature: Uint8Array; transaction: null }> => {
  if (typeof window === "undefined") {
    throw new Error("Kibisis is only available in the browser");
  }
  const kibisisMessage = "MX" + JSON.stringify(message);
  const kibisisResult = await window.algorand.signBytes({
    data: new Uint8Array(Buffer.from(kibisisMessage)),
  });

  return {
    signature: kibisisResult?.signature,
    transaction: null,
  };
};

Lute Wallet

Lute Wallet, like Defly, uses a transaction-based approach:

const signMessageWithLute = async (message: string, address: string): Promise<{ signature: Uint8Array; transaction: Uint8Array }> => {
  const hashedMessage = hashMessage(message);
  const encodedHashedMessage = getMessageBytes(Buffer.from(hashedMessage).toString("utf8"));

  const suggestedParams = await algodClient.getTransactionParams().do();
  const luteTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
    note: encodedHashedMessage,
    from: address,
    to: address,
    amount: 0,
    suggestedParams,
  });

  const luteSigArray = await luteWallet!.signTxns([
    { txn: Buffer.from(algosdk.encodeUnsignedTransaction(luteTxn)).toString("base64") },
  ]);

  const decodedLuteTxn = algosdk.decodeSignedTransaction(luteSigArray[0]);

  return {
    signature: decodedLuteTxn.sig as unknown as Uint8Array,
    transaction: luteSigArray[0],
  };
};

Unified Signing Function

Here's a unified function that handles signing for all supported wallet providers:

const signMessage = async (message: string): Promise<{ signature: Uint8Array; transaction?: any | null }> => {
  if (!address) {
    throw new Error("No address connected");
  }

  const hashedMessage = hashMessage(message);
  const encodedHashedMessage = getMessageBytes(Buffer.from(hashedMessage).toString("utf8"));

  const suggestedParams = ["Defly", "Lute"].includes(provider)
    ? await algodClient.getTransactionParams().do()
    : null;

  switch (provider) {
    case "Pera":
      return await signMessageWithPera(message, address);

    case "Defly":
      if (!suggestedParams) {
        throw new Error("Suggested params are not available");
      }
      return await signMessageWithDefly(message, address);

    case "Kibisis":
      return await signMessageWithKibisis(message);

    case "Lute":
      if (!suggestedParams) {
        throw new Error("Suggested params are not available");
      }
      return await signMessageWithLute(message, address);

    default:
      throw new Error("Unsupported wallet provider");
  }
};

This unified signMessage function determines the correct signing method based on the current wallet provider. It handles the differences in signing approaches between the wallets, ensuring a consistent interface for your application.

Remember to import and initialize the necessary wallet SDKs and utility functions (like hashMessage, getMessageBytes, and algodClient) before using these signing functions.

Last updated