Skip to content

Firmantes (Signers)

Si tu aplicación desea escribir datos en Farcaster en nombre de un usuario, la aplicación debe conseguir que el usuario añada una clave de firma para su aplicación.

Guía

Requisitos previos

  • Un FID registrado

1. Un usuario autenticado hace clic en "Conectar con Warpcast" en tu aplicación

Tu aplicación debe poder identificar y autenticar a un usuario antes de ofrecerle la opción de Conectar con Warpcast.

2. Genera un nuevo par de claves Ed25519 para el usuario y una firma SignedKeyRequest

Tu aplicación debe generar y almacenar de forma segura un par de claves Ed25519 asociado a este usuario. En los siguientes pasos, pedirás al usuario que apruebe este par de claves para firmar mensajes en su nombre.

Dado que este par de claves puede escribir en el protocolo en nombre del usuario, es importante que:

  • La clave privada se almacene de forma segura y nunca se exponga.
  • El par de claves pueda recuperarse y usarse para firmar mensajes cuando el usuario regrese.

Además de generar un nuevo par de claves, tu aplicación debe producir una firma ECDSA utilizando la dirección de custodia de su FID. Esto permite atribuir la clave a la aplicación, lo cual es útil para una amplia gama de cosas, desde saber qué aplicaciones se están utilizando hasta filtrar contenido basado en las aplicaciones que lo generaron.

Código de ejemplo:

ts
import * as ed from '@noble/ed25519';
import { mnemonicToAccount, signTypedData } from 'viem/accounts';

/*** Código auxiliar EIP-712 ***/

const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
  name: 'Farcaster SignedKeyRequestValidator',
  version: '1',
  chainId: 10,
  verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;

const SIGNED_KEY_REQUEST_TYPE = [
  { name: 'requestFid', type: 'uint256' },
  { name: 'key', type: 'bytes' },
  { name: 'deadline', type: 'uint256' },
] as const;

/*** Generando un par de claves ***/

const privateKey = ed.utils.randomPrivateKey();
const publicKeyBytes = await ed.getPublicKey(privateKey);
const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');

/*** Generando una firma Signed Key Request ***/

const appFid = process.env.APP_FID;
const account = mnemonicToAccount(process.env.APP_MNENOMIC);

const deadline = Math.floor(Date.now() / 1000) + 86400; // la firma es válida por 1 día
const signature = await account.signTypedData({
  domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
  types: {
    SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
  },
  primaryType: 'SignedKeyRequest',
  message: {
    requestFid: BigInt(appFid),
    key,
    deadline: BigInt(deadline),
  },
});

Fecha límite (Deadline)

Este valor controla cuánto tiempo es válida la firma Signed Key Request. Es una marca de tiempo Unix en segundos (nota: JavaScript usa milisegundos). Recomendamos establecerlo en 24 horas.

3. La aplicación usa la clave pública + firma SignedKeyRequest para iniciar una Solicitud de Clave Firmada usando la API de Warpcast

La aplicación llama al backend de Warpcast, que devuelve un enlace profundo (deeplink) y un token de sesión que puede usarse para verificar el estado de la solicitud.

ts
/*** Creando una Solicitud de Clave Firmada ***/

const warpcastApi = 'https://api.warpcast.com';
const { token, deeplinkUrl } = await axios
  .post(`${warpcastApi}/v2/signed-key-requests`, {
    key: publicKey,
    requestFid: fid,
    signature,
    deadline,
  })
  .then((response) => response.data.result.signedKeyRequest);

// deeplinkUrl debe presentarse al usuario
// token debe usarse para sondear

URL de redirección (Redirect URL)

Si esta solicitud se realiza desde una aplicación móvil nativa que puede abrir enlaces profundos, puedes incluir un redirectUrl al que el usuario debería ser redirigido después de completar la solicitud.

Nota: si tu aplicación es una PWA o aplicación web, no incluyas este valor, ya que el usuario será llevado a una sesión sin estado.

Patrocinios (Sponsorships)

Puedes patrocinar las tarifas onchain para el usuario. Consulta Patrocinar un firmante más abajo.

4. La aplicación presenta el enlace profundo de la respuesta al usuario

La aplicación presenta el enlace profundo, lo que pedirá al usuario que abra la aplicación Warpcast y autorice la solicitud del firmante (capturas de pantalla al final). La aplicación debe dirigir al usuario a abrir el enlace en su dispositivo móvil donde tenga instalado Warpcast:

  1. En móvil, activar el enlace profundo directamente.
  2. En web, mostrar el enlace profundo como un código QR para escanear.

Código de ejemplo

ts
import QRCode from 'react-qr-code';

const DeepLinkQRCode = (deepLinkUrl) => <QRCode value={deepLinkUrl} />;

5. La aplicación comienza a sondear el endpoint de Solicitud de Firmante usando el token

Una vez que se ha presentado el enlace profundo al usuario, la aplicación debe esperar a que el usuario complete el flujo de solicitud del firmante. La aplicación puede sondear el recurso de solicitud del firmante y buscar los datos que indiquen que el usuario ha completado la solicitud:

ts
const poll = async (token: string) => {
  while (true) {
    // esperar 1s
    await new Promise((r) => setTimeout(r, 2000));

    console.log('sondeando solicitud de clave firmada');
    const signedKeyRequest = await axios
      .get(`${warpcastApi}/v2/signed-key-request`, {
        params: {
          token,
        },
      })
      .then((response) => response.data.result.signedKeyRequest);

    if (signedKeyRequest.state === 'completed') {
      console.log('Solicitud de Clave Firmada completada:', signedKeyRequest);

      /**
       * En este punto, el firmante ha sido registrado onchain y puedes comenzar a enviar
       * mensajes a hubs firmados por su clave:
       * ```
       * const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
       * const message = makeCastAdd(..., signer)
       * ```
       */
      break;
    }
  }
};

poll(token);

6. El usuario abre el enlace y completa el flujo de Solicitud de Firmante en Warpcast

Cuando el usuario aprueba la solicitud en Warpcast, se realizará una transacción onchain que otorga permisos de escritura a ese firmante. Una vez que se complete, tu aplicación debería indicar éxito y puede comenzar a escribir mensajes usando la clave recién añadida.

Implementación de referencia

ts
import * as ed from '@noble/ed25519';
import { Hex } from 'viem';
import { mnemonicToAccount } from 'viem/accounts';
import axios from 'axios';
import * as qrcode from 'qrcode-terminal';

/*** Código auxiliar EIP-712 ***/

const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
  name: 'Farcaster SignedKeyRequestValidator',
  version: '1',
  chainId: 10,
  verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;

const SIGNED_KEY_REQUEST_TYPE = [
  { name: 'requestFid', type: 'uint256' },
  { name: 'key', type: 'bytes' },
  { name: 'deadline', type: 'uint256' },
] as const;

(async () => {
  /*** Generando un par de claves ***/

  const privateKey = ed.utils.randomPrivateKey();
  const publicKeyBytes = await ed.getPublicKey(privateKey);
  const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');

  /*** Generando una firma Signed Key Request ***/

  const appFid = process.env.APP_FID;
  const account = mnemonicToAccount(process.env.APP_MNEMONIC);

  const deadline = Math.floor(Date.now() / 1000) + 86400; // la firma es válida por 1 día
  const signature = await account.signTypedData({
    domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
    types: {
      SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
    },
    primaryType: 'SignedKeyRequest',
    message: {
      requestFid: BigInt(appFid),
      key,
      deadline: BigInt(deadline),
    },
  });

  /*** Creando una Solicitud de Clave Firmada ***/

  const warpcastApi = 'https://api.warpcast.com';
  const { token, deeplinkUrl } = await axios
    .post(`${warpcastApi}/v2/signed-key-requests`, {
      key,
      requestFid: appFid,
      signature,
      deadline,
    })
    .then((response) => response.data.result.signedKeyRequest);

  qrcode.generate(deeplinkUrl, console.log);
  console.log('escanea esto con tu teléfono');
  console.log('enlace profundo:', deeplinkUrl);

  const poll = async (token: string) => {
    while (true) {
      // esperar 1s
      await new Promise((r) => setTimeout(r, 2000));

      console.log('sondeando solicitud de clave firmada');
      const signedKeyRequest = await axios
        .get(`${warpcastApi}/v2/signed-key-request`, {
          params: {
            token,
          },
        })
        .then((response) => response.data.result.signedKeyRequest);

      if (signedKeyRequest.state === 'completed') {
        console.log('Solicitud de Clave Firmada completada:', signedKeyRequest);

        /**
         * En este punto, el firmante ha sido registrado onchain y puedes comenzar a enviar
         * mensajes a hubs firmados por su clave:
         * ```
         * const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
         * const message = makeCastAdd(..., signer)
         * ```
         */
        break;
      }
    }
  };

  await poll(token);
})();

API

POST /v2/signed-key-request

Crea una solicitud de clave firmada.

Cuerpo (Body):

  • key - cadena hexadecimal de la clave pública Ed25519
  • requestFid - fid de la aplicación solicitante
  • deadline - Marca de tiempo Unix hasta la cual la firma es válida
  • signature - Firma SignedKeyRequest de la aplicación solicitante
  • redirectUrl - Opcional. URL a la que redirigir después de que se apruebe el firmante. Nota: esto solo debe usarse cuando se solicita un firmante desde una aplicación móvil nativa.
  • sponsorship -

Respuesta de ejemplo:

json
{
  "result": {
    "signedKeyRequest": {
      "token": "0xa241e6b1287a07f4d3f9c5bd",
      "deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
      "key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
      "state": "pending"
    }
  }
}

GET /v2/signed-key-request

Obtiene el estado de una solicitud de clave firmada.

Parámetros de consulta (Query parameters):

  • token - token que identifica la solicitud

Respuesta:

  • token - token que identifica la solicitud
  • deeplinkUrl - URL donde el usuario puede completar la solicitud
  • key - clave solicitada para añadir
  • state - estado de la solicitud: pending - ninguna acción tomada por el usuario, approved - el usuario ha aprobado pero la transacción onchain no está confirmada, completed - la transacción onchain está confirmada

Respuesta de ejemplo en estado pendiente (pending):

json
{
  "result": {
    "signedKeyRequest": {
      "token": "0xa241e6b1287a07f4d3f9c5bd",
      "deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
      "key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
      "state": "pending"
    }
  }
}

Respuesta de ejemplo después de la aprobación pero antes de que la transacción esté confirmada:

json
{
  "result": {
    "signedKeyRequest": {
      "token": "0xa241e6b1287a07f4d3f9c5bd",
      "deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
      "key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
      "state": "approved",
      "userFid": 1
    }
  }
}

Respuesta de ejemplo después de que la transacción esté confirmada:

json
{
  "result": {
    "signedKeyRequest": {
      "token": "0xa241e6b1287a07f4d3f9c5bd",
      "deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
      "key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
      "state": "completed",
      "userFid": 1
    }
  }
}

Patrocinar un firmante (Sponsoring a signer)

Una aplicación puede patrocinar un firmante para que el usuario no tenga que pagar. La aplicación debe estar registrada en Warpcast y tener warps ≥ 100.

Al generar una solicitud de clave firmada, una aplicación puede indicar que debe ser patrocinada incluyendo un campo adicional sponsorship en el cuerpo de la solicitud.

ts
type SignedKeyRequestSponsorship = {
  sponsorFid: number;
  signature: string; // firma de patrocinio por sponsorFid
};

type SignedKeyRequestBody = {
  key: string;
  requestFid: number;
  deadline: number;
  signature: string; // firma de solicitud de clave por requestFid
  sponsorship?: SignedKeyRequestSponsorship;
};

Para crear un SignedKeyRequestSponsorship:

  1. Crea el par de claves y haz que el FID solicitante genere una firma.
  2. Crea una segunda firma del FID patrocinador usando la firma generada en el paso 1 como entrada cruda para un mensaje EIP-191.
ts
// sponsoringAccount es una instancia de cuenta Viem para la dirección de custodia del FID patrocinador
// signedKeyRequestSignature es la firma EIP-712 firmada por el FID solicitante
const sponsorSignature = sponsoringAccount.signMessage({
  message: { raw: signedKeyRequestSignature },
});

Cuando el usuario abra la solicitud de clave firmada en Warpcast, verá que las tarifas onchain han sido patrocinadas por tu aplicación.