> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clicker.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing Comments

> When a user creates a Swap Comment from a partner wallet, they also sign a message proving the comment is authentic.

The message to sign is:

```
Clicker Swap Comment:

Chain: ${chain}
Token Address: ${contractAddress}
Txn Hash: ${txnHash}
Comment: ${commentText}
```

To assist you with testing and debugging your swap comment signatures, we've provided the entire `signature-verification.ts` that we use in our own backend services and test suite.

<CodeGroup>
  ```typescript signature-verification.ts theme={null}
  import { getBase58Codec, getBase58Decoder, signBytes } from '@solana/kit';
  import { timingSafeEqual, verify } from 'crypto';
  import { createWalletClient, http, recoverMessageAddress } from 'viem';
  import { privateKeyToAccount } from 'viem/accounts';
  import { mainnet } from 'viem/chains';
  import { z } from 'zod';

  const solanaPrefixRegex = /^(solana:)/;
  const EVMAddress = z
    .string()
    .regex(/^0x[a-zA-Z0-9]{40}$/, 'Must be an Ethereum address starting with 0x')
    .transform(a => a.toLowerCase())
    .openapi({ type: 'string' });


  export const removeSolPrefixTransformer = (val: string) => val.replace(solanaPrefixRegex, '');
  export const isValidEVMAddress = (value: string) => EVMAddress.safeParse(value).success;

  export const SWAP_COMMENT_MESSAGE_PREFIX = 'Clicker Swap Comment:';

  export const createSwapCommentMessage = (
    chain: string,
    tokenAddress: string,
    txnHash: string,
    commentText: string
  ): string => {
    // If you find yourself updating this, update the mintilfy docs page too!
    const contractAddress = isValidEVMAddress(tokenAddress) ? tokenAddress.toLowerCase() : tokenAddress;
    return `${SWAP_COMMENT_MESSAGE_PREFIX}\n\nChain: ${chain}\nToken Address: ${contractAddress}\nTxn Hash: ${txnHash}\nComment: ${commentText}`;
  };

  // This is a test helper function - in production, signing should happen client-side
  export const signMessage = async (message: string, privateKey: string | CryptoKeyPair): Promise<{ signature: string; signerAddress: string }> => {
    if (typeof privateKey === 'object' && 'privateKey' in privateKey) {
      // Solana signing using CryptoKeyPair from @solana/kit
      const signedBytes = await signBytes(privateKey.privateKey, Buffer.from(message, 'utf-8'));
      const signature = getBase58Decoder().decode(signedBytes);
      const signerAddress = getBase58Decoder().decode(
        Buffer.from(await crypto.subtle.exportKey('raw', privateKey.publicKey))
      );
      
      return { signature, signerAddress };
    } else if (typeof privateKey === 'string' && privateKey.startsWith('0x')) {
      // EVM signing using viem
      const account = privateKeyToAccount(privateKey as `0x${string}`);
      const client = createWalletClient({
        account,
        chain: mainnet,
        transport: http(),
      });
      
      const signature = await client.signMessage({ message });
      
      return { signature, signerAddress: account.address };
    } else {
      throw new Error('Invalid private key format. Expected EVM private key (0x...) or Solana CryptoKeyPair');
    }
  };

  export const verifySwapCommentSignature = async (
    chain: string,
    tokenAddress: string,
    txnHash: string,
    commentText: string,
    signature: string,
    signerAddress: string
  ): Promise<boolean> => {
    try {
      const contractAddress = isValidEVMAddress(tokenAddress) ? tokenAddress.toLowerCase() : tokenAddress;
      const message = createSwapCommentMessage(chain, contractAddress, txnHash, commentText);

      if (signerAddress.startsWith('solana:')) {
        const publicKeyBytes = getBase58Codec().encode(removeSolPrefixTransformer(signerAddress));
        const signatureBytes = getBase58Codec().encode(signature);
        return verify(
          null, // No digest algorithm for Ed25519
          Buffer.from(message, 'utf-8'),
          {
            key: Buffer.concat([
              Buffer.from('302a300506032b6570032100', 'hex'), // ASN.1 DER prefix for Ed25519
              Buffer.from(publicKeyBytes),
            ]),
            format: 'der',
            type: 'spki',
          },
          Buffer.from(signatureBytes)
        );
      } else {
        const formattedSignature = signature.startsWith('0x') ? signature : `0x${signature}`;
        const recoveredAddress = await recoverMessageAddress({
          message,
          signature: formattedSignature as `0x${string}`,
        });

        // Convert addresses to Buffer for constant-time comparison
        const recoveredBuffer = Buffer.from(recoveredAddress.toLowerCase().slice(2), 'hex');
        const expectedBuffer = Buffer.from(signerAddress.toLowerCase().slice(2), 'hex');

        return (
          recoveredBuffer.length === expectedBuffer.length &&
          timingSafeEqual(recoveredBuffer, expectedBuffer)
        );
      }
    } catch (error) {
      console.error('Signature verification failed:', error);
      return false;
    }
  };
  ```
</CodeGroup>
