Acciones y Blinks

Solana Actions o Acciones son APIs que cumplen con especificaciones que devuelven transacciones en la cadena de bloques de Solana para ser previsualizadas, firmadas y enviadas a través de un número diverso de contextos, incluyendo códigos QR, botones + widgets y sitios web a través de Internet. Las Acciones facilitan a los desarrolladores la integración de todo lo que puedes hacer en el ecosistema de Solana directo en su entorno actual, permitiéndoles realizar transacciones en la cadena de bloques sin la necesidad de navegar a otra aplicación o página web.

Los enlaces Blockchain – o blinks – convierten cualquier Solana Action en un enlace compartible y rico en metadatos. Los Blinks permiten a los clientes compatibles con Acciones (como extensiones de billetera del navegador o bots) mostrar capacidades adicionales para el usuario. En un sitio web, un blink podría activar inmediatamente una vista previa de la transacción en una billetera sin tener que ir a una aplicación descentralizada; en Discord, un bot podría ampliar el blink en un conjunto interactivo de botones. Esto brinda la capacidad de interactuar con la cadena de bloques a cualquier superficie web capaz de mostrar una URL.

Primeros Pasos #

Para comenzar rápidamente a crear Acciones personalizadas en Solana:

npm install @solana/actions
Info

Echa un vistazo a este tutorial de cómo construir una Acción de Solana usando el SDK @solana/actions.

También puedes encontrar el código fuente para una Acción que realiza una transferencia SOL nativa y varias otras Acciones de ejemplo en este repo.

Al desplegar tus Acciones personalizadas de Solana en producción:

Si estás buscando inspiración para construir Acciones y Blinks, revisa el repositorio "Awesome Blinks" que contiene algunas creaciones de la comunidad e incluso ideas para otras nuevas.

Acciones #

La especificación de Solana Actions utiliza un conjunto de APIs estándar para entregar transacciones que pueden ser firmadas (y eventualmente mensajes firmables) desde una aplicación directamente a un usuario. Están alojadas en URLs de acceso público y, por tanto, son accesibles por su URL para que cualquier cliente pueda interactuar con ellas.

Info

Puedes pensar en Acciones como un endpoint de la API que devolverá metadatos y algo para que un usuario firme (ya sea una transacción o un mensaje de autenticación) con su billetera de la cadena de bloques.

La API de Acciones consiste en realizar solicitudes simples GET y POST al endpoint URL de una Acción y gestionar las respuestas que cumplen con la interfaz de Acciones.

  1. La solicitud GET devuelve metadatos que proporcionan información legible para el cliente sobre qué acciones están disponibles en esta URL, y una lista opcional de acciones relacionadas.
  2. the POST request returns a signable transaction or message that the client then prompts the user's wallet to sign and execute on the blockchain or in another offchain service.

Ejecución y ciclo de vida de las Acciones #

En la práctica, interactuar con Acciones se parece mucho a interactuar con una API REST típica:

  • Un cliente realiza una solicitud inicial GET a la URL de una Acción para obtener metadatos sobre las Acciones disponibles
  • El endpoint devuelve una respuesta que incluye metadatos de la acción (como el título y el icono de la aplicación) y una lista de las acciones disponibles para este endpoint
  • La aplicación cliente (como una billetera móvil, un chatbot o un sitio web) muestra una interfaz de usuario para que el usuario realice una de las acciones
  • Después de que el usuario seleccione una acción (haciendo clic en un botón), el cliente realiza una solicitud POST al endpoint para obtener la transacción que el usuario debe firmar
  • La billetera facilita al usuario la firma de la transacción y, en última instancia, envía la transacción a la cadena de bloques para su confirmación

Solana Actions Execution and LifecycleSolana Actions Execution and Lifecycle

Al recibir transacciones de una URL de Acciones, los clientes deben gestionar el envío de estas transacciones a la cadena de bloques y gestionar su ciclo de vida.

Las Acciones también admiten cierto nivel de invalidación antes de la ejecución. Las peticiones GET y POST pueden devolver algunos metadatos que indiquen si la acción puede realizarse (como el campo disabled).

Por ejemplo: si hubiera una Acción que facilitara la votación de una propuesta de gobernanza de una DAO cuya ventana de votación se hubiera cerrado, la solicitud GET inicial podría devolver el mensaje de error "Esta propuesta ya no está disponible para votación" y los botones "Votar sí" y "Votar no" se mostrarían como "desactivados".

Los Blinks (enlaces de blockchain) son aplicaciones cliente que introspeccionan las API de las Acciones y construyen interfaces de usuario en torno a la interacción y ejecución de las Acciones.

Las aplicaciones cliente que admiten blinks simplemente detectan las URL compatibles con Acciones, las analizan y permiten a los usuarios interactuar con ellas en interfaces de usuario estandarizadas.

Info

Cualquier aplicación cliente que realice una introspección completa de una API de Acciones para construir una interfaz completa para ella es un blink. Por lo tanto, no todos los clientes que consumen una API de Acciones son blinks.

Una URL blink describe una aplicación cliente que permite a un usuario completar el ciclo de vida de una Acción, incluyendo la firma con su billetera.

https://example.domain/?action=<action_url>

Para que cualquier aplicación cliente se convierta en un blink:

  • La URL de un blink debe contener el parámetro action cuyo valor sea una URL codificada como URL de una Acción. Este valor debe ser codificado como URL para no entrar en conflicto con ningún otro parámetro del protocolo.

  • La aplicación cliente debe decodificar la URL del parámetro action e introspeccionar la API de acción correspondiente (véase URL de una Acción).

  • El cliente debe ofrecer una interfaz de usuario que permita completar el ciclo de vida de una Acción, incluida la firma con su billetera.

Info

No todas las aplicaciones cliente blink (por ejemplo: sitios web o dApps) soportarán todas las Acciones. Los desarrolladores de aplicaciones pueden elegir las Acciones que desean admitir en sus interfaces blink.

El siguiente ejemplo muestra una URL blink con el valor action de solana-action:https://actions.alice.com/donar codificado como URL:

https://example.domain/?action=solana-action%3Ahttps%3A%2F%2Factions.alice.com%2Fdonate

Los blinks pueden ser enlazados a las Acciones en al menos 3 formas:

  1. Compartir una URL de acción explícita: solana-action:https://actions.alice.com/donar

    En este caso, solo los clientes compatibles pueden renderizar el blink. No habrá enlace de previsualización alternativa, o sitio que pueda ser visitado fuera del cliente compatible.

  2. Compartir un enlace a un sitio web vinculado a una API de Acciones a través de un archivo actions.json en la raíz del dominio del sitio web.

    Por ejemplo: https://alice.com/actions.json asigna https://alice.com/donar, la URL de un sitio web en el que los usuarios pueden donar a Alice, a la URL de la API https://actions.alice.com/donar, en la que se alojan las Acciones para donar a Alice.

  3. Incrustar una URL de Acción en la URL de un sitio “interstitial” que sepa cómo interpretar las Acciones.

    https://example.domain/?action=<action_url>

Los clientes que admiten blinks deben ser capaces de tomar cualquiera de los formatos anteriores y renderizar una interfaz para facilitar la ejecución de la acción directamente en el cliente.

Para los clientes que no admiten blinks, debe haber un sitio web subyacente (haciendo que el navegador se convierta en la alternativa universal).

Si un usuario pulsa en cualquier parte de un cliente que no sea un botón de acción o un campo de entrada de texto debe ir al sitio web subyacente.

Mientras que las Acciones y Blinks de Solana son un protocolo/especificación que no requieren permisos, las aplicaciones cliente y las billeteras siguen siendo requeridas para facilitar a los usuarios firmar la transacción.

Info

Use the Blinks Inspector tool to inspect, debug, and test your blinks and actions directly in your browser. You can view the GET and POST response payloads, response headers, and test all inputs to each of your linked Actions.

Each client application or wallets may have different requirements on which Action endpoints their clients will automatically unfurl and immediately display to their users on social media platforms.

Por ejemplo: algunos clientes pueden operar con un enfoque de "lista de permitidos" que puede requerir verificación antes de que su cliente implemente una Acción para usuarios como el Registro de Acciones de Dialect (detallado a continuación).

Todos los Blinks seguirán mostrándose y permitiendo firmar en el sitio intersticial de Dialect dial.to, con su estado de registro mostrado en el Blink.

Registro de Acciones de Dialect #

Como un bien público para el ecosistema de Solana, Dialect mantiene un registro público — junto con la ayuda de la Fundación Solana y otros miembros de la comunidad — de enlaces blockchain que tienen procedencia de fuentes conocidas previamente verificadas. A partir del lanzamiento, solo las Acciones que se hayan registrado en el registro de Dialect se desplegarán en Twitter cuando se publique.

Las aplicaciones cliente y billeteras pueden elegir libremente utilizar este registro público u otra solución para ayudar a garantizar la seguridad del usuario. Si no se verifica a través del registro de Dialect, el enlace blockchain no será tocado por el cliente Blink y se representará como una URL típica.

Los desarrolladores pueden solicitar ser verificados por Dialect aquí.

Especificación #

La especificación de Solana Actions consta de secciones clave que forman parte de un flujo de interacción solicitud/respuesta:

Cada una de estas solicitudes las realiza el Cliente de Acciones (por ejemplo: aplicación de una billetera, extensión de navegador, dApp, sitio web, etc.) para recopilar metadatos específicos para interfaces de usuario y facilitar la entrada de datos del usuario en la API de Acciones.

Cada una de las respuestas es elaborada por una aplicación (por ejemplo: sitio web, servidor backend, etc.) y devuelta al Cliente de Acciones. En última instancia, proporciona una transacción o mensaje firmable para que una billetera pida al usuario que lo apruebe, firme y se envíe a la cadena de bloques.

Info

The types and interfaces declared within this readme files are often the simplified version of the types to aid in readability.

For better type safety and improved developer experience, the @solana/actions-spec package contains more complex type definitions. You can find the source code for them here.

Esquema de URL #

Una URL de Acción de Solana describe una solicitud interactiva de una transacción o mensaje de Solana firmable mediante el protocolo solana-action.

La solicitud es interactiva porque los parámetros de la URL son utilizados por un cliente para realizar una serie de solicitudes HTTP estandarizadas para componer una transacción o mensaje firmable para que el usuario lo firme con su billetera.

solana-action:<link>
  • Se requiere un único campo link como nombre de ruta. El valor debe ser un URL HTTPS absoluto condicionalmente codificado.

  • Si la URL contiene parámetros de consulta, deben estar codificados como URL. La codificación URL del valor evita conflictos con cualquier parámetro del protocolo que puede añadirse a través de la especificación.

  • Si la URL no contiene parámetros de consulta, no debe codificarse como URL. Esto produce una URL más corta y un código QR menos denso.

En ambos casos, los clientes deben decodificar el valor. Esto no tiene ningún efecto si el valor no está codificado como URL. Si el valor decodificado no es una URL HTTPS absoluta, la billetera debe rechazarlo como malformado.

Respuesta OPTIONS #

Para permitir el Intercambio de Recursos entre Orígenes (o CORS por sus siglas en inglés) dentro de Acciones clientes (incluidos los blinks), todos los endpoints de Acción deben responder a las solicitudes HTTP para el método OPTIONS con cabeceras válidas que permitirán a los clientes pasar comprobaciones CORS para todas las solicitudes posteriores de su mismo dominio de origen.

Un cliente de Acciones puede realizar solicitudes de "verificación previa" al endpoint de la URL de Acción para verificar si la solicitud GET subsecuente a la URL de Acción pasará todas las comprobaciones de CORS. Estas verificaciones previas de CORS se realizan utilizando el método HTTP OPTIONS y deben responder con todas las cabeceras HTTP requeridas que permitirán a los clientes de Acción (como los blinks) realizar correctamente todas las solicitudes posteriores desde su dominio de origen.

Como mínimo, las cabeceras HTTP requeridas incluyen:

  • Access-Control-Allow-Origin con el valor *
    • esto garantiza que todos los clientes de Acción puedan pasar las comprobaciones CORS de forma segura para realizar todas las solicitudes requeridas
  • Access-Control-Allow-Methods con el valor GET,POST,PUT,OPTIONS
    • asegura que todos los métodos requeridos de peticiones HTTP sean compatibles con Acciones
  • Access-Control-Allow-Headers con un valor mínimo de Content Type, Authorization, Content-Encoding, Accept-Encoding

Por simplicidad, los desarrolladores deben considerar devolver la misma respuesta y cabecera a las solicitudes OPTIONS como su respuesta GET.

Cross-Origin headers for actions.json

La respuesta del archivo actions.json también debe devolver cabeceras de origen cruzado válidas para las solicitudes GET y OPTIONS, específicamente el valor de la cabecera Access-Control-Allow-Origin de *.

Consulte actions.json abajo para más detalles.

Solicitud GET #

El cliente de la Acción (por ejemplo: una billetera, extensión del navegador, etc.) debe realizar una solicitud HTTP GET JSON al endpoint URL de la Acción.

  • La solicitud no debe identificar la billetera ni al usuario.
  • El cliente debe realizar la solicitud con una cabecera Accept-Encoding.
  • El cliente debe mostrar el dominio de la URL mientras se realiza la solicitud.

Respuesta GET #

El endpoint de la URL de la Acción (por ejemplo: aplicación o servidor backend) debe responder con una respuesta HTTP OK JSON (con una información válida en el cuerpo) o un error HTTP apropiado.

Error responses (i.e. HTTP 4xx and 5xx status codes) should return a JSON response body following ActionError to present a helpful error message to users. See Action Errors.

Cuerpo de la respuesta GET #

Una respuesta GET con una respuesta HTTP OK JSON debe incluir un cuerpo que siga la especificación de la interfaz:

ActionGetResponse
export type ActionType = "action" | "completed";
 
export type ActionGetResponse = Action<"action">;
 
export interface Action<T extends ActionType> {
  /** type of Action to present to the user */
  type: T;
  /** image url that represents the source of the action request */
  icon: string;
  /** describes the source of the action request */
  title: string;
  /** brief summary of the action to be performed */
  description: string;
  /** button text rendered to the user */
  label: string;
  /** UI state for the button being rendered to the user */
  disabled?: boolean;
  links?: {
    /** list of related Actions a user could perform */
    actions: LinkedAction[];
  };
  /** non-fatal error message to be displayed to the user */
  error?: ActionError;
}
  • type - The type of action being given to the user. Defaults to action. The initial ActionGetResponse is required to have a type of action.

    • action - Standard action that will allow the user to interact with any of the LinkedActions
    • completed - Used to declare the "completed" state within action chaining.
  • icon - El valor debe ser una URL HTTP o HTTPS absoluta de una imagen que representa un icono. El archivo debe ser una imagen SVG, PNG o WebP, o el cliente/billetera debe rechazarlo como malformado.

  • title - El valor debe ser una cadena de caracteres UTF-8 que represente el origen de la solicitud de la acción. Por ejemplo: podría ser el nombre de una marca, tienda, aplicación o persona que realiza la solicitud.

  • description - El valor debe ser una cadena de caracteres UTF-8 que proporcione información sobre la acción. La descripción debe mostrarse al usuario.

  • label - El valor debe ser una cadena de caracteres UTF-8 que se mostrará en un botón para que el usuario haga clic. Ninguna de las etiquetas deben superar las 5 palabras y deben comenzar con un verbo para informar al usuario de la acción a realizar. Por ejemplo: "Votar Si" o "Depositar fondos".

  • disabled - El valor debe ser booleano para representar el estado desactivado del botón renderizado (que muestra la etiqueta). Si no se proporciona ningún valor, disabled debe ser por defecto false (es decir, activado por defecto). Por ejemplo: si el endpoint de la acción es para una votación de gobernanza que se ha cerrado, establezca disabled=true y la label podría ser "Voto cerrado".

  • error - Una indicación de error opcional para errores no fatales. Si está presente, el cliente debe mostrarlo al usuario. If set, it should not prevent the client from interpreting the action or displaying it to the user (see Action Errors). For example, the error can be used together with disabled to display a reason like business constraints, authorization, the state, or an error of external resource.

  • links.actions - Un arreglo opcional de acciones relacionadas para el endpoint. Se debe mostrar a los usuarios una UI para cada una de las acciones enlistadas y esperar que solo una sea seleccionada para realizarse. Por ejemplo: un endpoint de acción de votación de gobernanza puede devolver tres opciones al usuario como "Votar Sí", "Votar No" y "Abstenerse de votar".

    • Si no se proporciona links.actions, el cliente debe mostrar un solo botón usando el string raíz label y realizar la solicitud POST al mismo endpoint que la solicitud GET inicial.

    • Si se proporciona algún links.actions, el cliente solo debe mostrar botones y campos de entrada basados ​​en los elementos enumerados en el campo links.actions. El cliente no debe mostrar un botón para el contenido del label raíz.

LinkedAction
export interface LinkedAction {
  /** URL endpoint for an action */
  href: string;
  /** button text rendered to the user */
  label: string;
  /**
   * Parameters to accept user input within an action
   * @see {ActionParameter}
   * @see {ActionParameterSelectable}
   */
  parameters?: Array<TypedActionParameter>;
}

The ActionParameter allows declaring what input the Action API is requesting from the user:

ActionParameter
/**
 * Parameter to accept user input within an action
 * note: for ease of reading, this is a simplified type of the actual
 */
export interface ActionParameter {
  /** input field type */
  type?: ActionParameterType;
  /** parameter name in url */
  name: string;
  /** placeholder text for the user input field */
  label?: string;
  /** declare if this field is required (defaults to `false`) */
  required?: boolean;
  /** regular expression pattern to validate user input client side */
  pattern?: string;
  /** human-readable description of the `type` and/or `pattern`, represents a caption and error, if value doesn't match */
  patternDescription?: string;
  /** the minimum value allowed based on the `type` */
  min?: string | number;
  /** the maximum value allowed based on the `type` */
  max?: string | number;
}

The pattern should be a string equivalent of a valid regular expression. This regular expression pattern should by used by blink-clients to validate user input before making the POST request. If the pattern is not a valid regular expression, it should be ignored by clients.

The patternDescription is a human readable description of the expected input requests from the user. If pattern is provided, the patternDescription is required to be provided.

The min and max values allows the input to set a lower and/or upper bounds of the input requested from the user (i.e. min/max number and or min/max character length), and should be used for client side validation. For input types of date or datetime-local, these values should be a string dates. For other string based input types, the values should be numbers representing their min/max character length.

If the user input value is not considered valid per the pattern, the user should receive a client side error message indicating the input field is not valid and displayed the patternDescription string.

The type field allows the Action API to declare more specific user input fields, providing better client side validation and improving the user experience. In many cases, this type will resemble the standard HTML input element.

The ActionParameterType can be simplified to the following type:

ActionParameterType
/**
 * Input field type to present to the user
 * @default `text`
 */
export type ActionParameterType =
  | "text"
  | "email"
  | "url"
  | "number"
  | "date"
  | "datetime-local"
  | "checkbox"
  | "radio"
  | "textarea"
  | "select";

Each of the type values should normally result in a user input field that resembles a standard HTML input element of the corresponding type (i.e. <input type="email" />) to provide better client side validation and user experience:

  • text - equivalent of HTML “text” input element
  • email - equivalent of HTML “email” input element
  • url - equivalent of HTML “url” input element
  • number - equivalent of HTML “number” input element
  • date - equivalent of HTML “date” input element
  • datetime-local - equivalent of HTML “datetime-local” input element
  • checkbox - equivalent to a grouping of standard HTML “checkbox” input elements. The Action API should return options as detailed below. The user should be able to select multiple of the provided checkbox options.
  • radio - equivalent to a grouping of standard HTML “radio” input elements. The Action API should return options as detailed below. The user should be able to select only one of the provided radio options.
  • Other HTML input type equivalents not specified above (hidden, button, submit, file, etc) are not supported at this time.

In addition to the elements resembling HTML input types above, the following user input elements are also supported:

  • textarea - equivalent of HTML textarea element. Allowing the user to provide multi-line input.
  • select - equivalent of HTML select element, allowing the user to experience a “dropdown” style field. The Action API should return options as detailed below.

When type is set as select, checkbox, or radio then the Action API should include an array of options that each provide a label and value at a minimum. Each option may also have a selected value to inform the blink-client which of the options should be selected by default for the user (see checkbox and radio for differences).

This ActionParameterSelectable can be simplified to the following type definition:

ActionParameterSelectable
/**
 * note: for ease of reading, this is a simplified type of the actual
 */
interface ActionParameterSelectable extends ActionParameter {
  options: Array<{
    /** displayed UI label of this selectable option */
    label: string;
    /** value of this selectable option */
    value: string;
    /** whether or not this option should be selected by default */
    selected?: boolean;
  }>;
}

If no type is set or an unknown/unsupported value is set, blink-clients should default to text and render a simple text input.

The Action API is still responsible to validate and sanitize all data from the user input parameters, enforcing any “required” user input as necessary.

For platforms other that HTML/web based ones (like native mobile), the equivalent native user input component should be used to achieve the equivalent experience and client side validation as the HTML/web input types described above.

Ejemplo de respuesta GET #

El siguiente ejemplo de respuesta proporciona una única acción "raíz" que se espera que presente al usuario un único botón con la etiqueta "Reclamar token de acceso":

{
  "title": "Eventos HackerHouse,
  "icon": "<url-to-image>",
  "description": "Reclama tu token de acceso para la Hackerhouse.",
  "label": "Reclamar token de acceso" // texto del botón
}

El siguiente ejemplo de respuesta proporciona 3 enlaces de acción relacionados que permiten al usuario hacer clic en uno de los 3 botones para emitir su voto para una propuesta en una DAO:

{
  "title": "Plataforma Realms para DAOs",
  "icon": "<url-to-image>",
  "description": "Vota en la propuesta de gobernanza #1234 de la DAO.",
  "label": "Votar",
  "links": {
    "actions": [
      {
        "label": "Votar Si", // texto del botón
        "href": "/api/propuesta/1234/votar?respuesta=si"
      },
      {
        "label": "Votar No", // texto del botón
        "href": "/api/propuesta/1234/votar?respuesta=no"
      },
      {
        "label": "Abstenerse de votar", // texto del botón
        "href": "/api/propuesta/1234/votar?respuesta=nulo"
      }
    ]
  }
}

Ejemplo de respuesta GET con parámetros #

Los siguientes ejemplos demuestran cómo aceptar la entrada de texto del usuario (a través de parameters) e incluir esa entrada en la petición POST (a través del campo href dentro de un LinkedAction):

El siguiente ejemplo de respuesta proporciona al usuario 3 acciones vinculadas para hacer stake de SOL: un botón denominado "Stake 1 SOL", otro botón denominado "Stake 5 SOL" y un campo de entrada de texto que permite al usuario introducir un valor específico que se enviará a la API de Acciones:

{
  "title": "Stake-o-matic",
  "icon": "<url-to-image>",
  "description": "Stake SOL para ayudar a asegurar la red de Solana.",
  "label": "Stake SOL", // no se muestra ya que `links.actions` existe
  "links": {
    "actions": [
      {
        "label": "Stake 1 SOL", // texto del boton
        "href": "/api/stake?amount=1"
        // no hay `parameters` por lo que no es campo de texto
      },
      {
        "label": "Stake 5 SOL", // button text
        "href": "/api/stake?amount=5"
        // no hay `parameters` por lo que no es campo de texto
      },
      {
        "label": "Stake", // texto del boton
        "href": "/api/stake?amount={amount}",
        "parameters": [
          {
            "name": "cantidad", // nombre del campo
            "label": "cantidad en SOL" // placeholder del campo de texto
          }
        ]
      }
    ]
  }
}

El siguiente ejemplo de respuesta proporciona un único campo de entrada para que el usuario introduzca un monto que se envía con la solicitud POST (como parámetro de consulta o puede utilizarse una sub-ruta):

{
  "icon": "<url-to-image>",
  "label": "Donar SOL",
  "title": "Dona a la Caridad",
  "description": "Apoya a esta fundación por medio de donaciones en SOL.",
  "links": {
    "actions": [
      {
        "label": "Donar", // texto del botón
        "href": "/api/donar/{monto}", // o /api/donar?monto={monto}
        "parameters": [
          // {monto} campo de entrada
          {
            "name": "monto", // nombre del campo de la entrada
            "label": "Monto en SOL" // placeholder de la entrada de texto
          }
        ]
      }
    ]
  }
}

Solicitud POST #

El cliente debe hacer una petición HTTP POST JSON a la URL de la acción con un body de:

{
  "account": "<account>"
}
  • account - El valor debe ser la clave pública codificada en base58 de una cuenta que pueda firmar la transacción.

El cliente debe realizar la solicitud con una cabecera Accept-Encoding y la aplicación puede responder con una cabecera Content-Encoding para la compresión HTTP.

El cliente debe mostrar el dominio de la URL de la acción mientras se realiza la solicitud. Si se ha realizado una petición GET, el cliente también debe mostrar el title y renderizar la imagen icon de esa respuesta GET.

Respuesta POST #

El endpoint POST de la Acción debe responder con una respuesta JSON HTTP OK (con un payload válido en el cuerpo) o un error HTTP apropiado.

Error responses (i.e. HTTP 4xx and 5xx status codes) should return a JSON response body following ActionError to present a helpful error message to users. See Action Errors.

Cuerpo de la respuesta POST #

Una respuesta POST con una respuesta HTTP OK JSON debe incluir un cuerpo de:

ActionPostResponse
/**
 * Response body payload returned from the Action POST Request
 */
export interface ActionPostResponse<T extends ActionType = ActionType> {
  /** base64 encoded serialized transaction */
  transaction: string;
  /** describes the nature of the transaction */
  message?: string;
  links?: {
    /**
     * The next action in a successive chain of actions to be obtained after
     * the previous was successful.
     */
    next: NextActionLink;
  };
}
  • transaction - The value must be a base64-encoded serialized transaction. The client must base64-decode the transaction and deserialize it.

  • message - El valor debe ser una cadena de caracteres UTF-8 que describa la naturaleza de la transacción incluida en la respuesta. El cliente debe mostrar este valor al usuario. Por ejemplo: puede ser el nombre de un artículo que se compra, un descuento aplicado a una compra o una nota de agradecimiento.

  • links.next - An optional value use to "chain" multiple Actions together in series. After the included transaction has been confirmed on-chain, the client can fetch and render the next action. See Action Chaining for more details.

  • El cliente y la aplicación deben permitir campos adicionales en el cuerpo de la solicitud y en el cuerpo de la respuesta, que pueden ser añadidos por futuras actualizaciones de la especificación.

Info

La aplicación puede responder con una transacción parcial o totalmente firmada. El cliente y la billetera deben validar la transacción como no confiable.

Respuesta POST - Transacción #

If the transaction signatures are empty or the transaction has NOT been partially signed:

  • The client must ignore the feePayer in the transaction and set the feePayer to the account in the request.
  • The client must ignore the recentBlockhash in the transaction and set the recentBlockhash to the latest blockhash.
  • El cliente debe serializar y deserializar la transacción antes de firmarla. Esto garantiza un orden coherente de las cuentas, como solución para este problema.

Si la transacción ha sido parcialmente firmada:

  • The client must NOT alter the feePayer or recentBlockhash as this would invalidate any existing signatures.
  • El cliente debe verificar las firmas existentes y, si alguna no es válida, el cliente debe rechazar la transacción como mal formada.

El cliente sólo debe firmar la transacción con el account en la solicitud, y debe hacerlo sólo si se espera una firma para ese account en la solicitud.

Si se espera alguna firma excepto la firma del account en la solicitud, el cliente debe rechazar la transacción como maliciosa.

Action Errors #

Actions APIs should return errors using ActionError in order to present helpful error messages to the user. Depending on the context, this error could be fatal or non-fatal.

ActionError
export interface ActionError {
  /** simple error message to be displayed to the user */
  message: string;
}

When an Actions API responds with an HTTP error status code (i.e. 4xx and 5xx), the response body should be a JSON payload following ActionError. The error is considered fatal and the included message should be presented to the user.

For API responses that support the optional error attribute (like ActionGetResponse), the error is considered non-fatal and the included message should be presented to the user.

Action Chaining #

Solana Actions can be "chained" together in a successive series. After an Action's transaction is confirmed on-chain, the next action can be obtained and presented to the user.

Action chaining allows developers to build more complex and dynamic experiences within blinks, including:

  • providing multiple transactions (and eventually sign message) to a user
  • customized action metadata based on the user's wallet address
  • refreshing the blink metadata after a successful transaction
  • receive an API callback with the transaction signature for additional validation and logic on the Action API server
  • customized "success" messages by updating the displayed metadata (e.g. a new image and description)

To chain multiple actions together, in any ActionPostResponse include a links.next of either:

  • PostNextActionLink - POST request link with a same origin callback url to receive the signature and user's account in the body. This callback url should respond with a NextAction.
  • InlineNextActionLink - Inline metadata for the next action to be presented to the user immediately after the transaction has confirmed. No callback will be made.
export type NextActionLink = PostNextActionLink | InlineNextActionLink;
 
/** @see {NextActionPostRequest} */
export interface PostNextActionLink {
  /** Indicates the type of the link. */
  type: "post";
  /** Relative or same origin URL to which the POST request should be made. */
  href: string;
}
 
/**
 * Represents an inline next action embedded within the current context.
 */
export interface InlineNextActionLink {
  /** Indicates the type of the link. */
  type: "inline";
  /** The next action to be performed */
  action: NextAction;
}

NextAction #

After the ActionPostResponse included transaction is signed by the user and confirmed on-chain, the blink client should either:

  • execute the callback request to fetch and display the NextAction, or
  • if a NextAction is already provided via links.next, the blink client should update the displayed metadata and make no callback request

If the callback url is not the same origin as the initial POST request, no callback request should be made. Blink clients should display an error notifying the user.

NextAction
/** The next action to be performed */
export type NextAction = Action<"action"> | CompletedAction;
 
/** The completed action, used to declare the "completed" state within action chaining. */
export type CompletedAction = Omit<Action<"completed">, "links">;

Based on the type, the next action should be presented to the user via blink clients in one of the following ways:

  • action - (default) A standard action that will allow the user to see the included Action metadata, interact with the provided LinkedActions, and continue to chain any following actions.

  • completed - The terminal state of an action chain that can update the blink UI with the included Action metadata, but will not allow the user to execute further actions.

If links.next is not provided, blink clients should assume the current action is final action in the chain, presenting their "completed" UI state after the transaction is confirmed.

actions.json #

El propósito del archivo actions.json es permitirle a una aplicación indicar a los clientes qué URL de sitios web admiten Acciones de Solana y proporcionar una asignación que pueda utilizarse para realizar solicitudes GET a un servidor de API de Acciones.

Cross-Origin headers are required

La respuesta del archivo actions.json también debe devolver cabeceras de Origen Cruzado válidas para las solicitudes GET y OPTIONS, específicamente el valor de la cabecera Access-Control-Allow-Origin de *.

Vea Respuesta OPTIONS arriba para más detalles.

El archivo actions.json debe almacenarse y ser accesible universalmente en la raíz del dominio.

Por ejemplo: si su aplicación web se despliega en mi-sitio.com, el archivo actions.json debe estar accesible en https://mi-sitio.com/actions.json. Este archivo también debe ser accesible desde orígenes cruzados a través de cualquier navegador teniendo un valor de cabecera Access-Control-Allow-Origin de *.

Reglas #

El campo rules (reglas) permite a la aplicación asignar un conjunto de rutas relativas de un sitio web a un conjunto de otras rutas.

Type: Array de ActionRuleObject.

ActionRuleObject
interface ActionRuleObject {
  /** ruta relativa (preferida) o absoluta para realizar la asignación de reglas  */
  pathPattern: string;
  /** ruta relativa (preferida) o absoluta que admita solicitudes de acción */
  apiPath: string;
}
  • pathPattern - Un patrón que coincide con cada nombre de ruta entrante.

  • apiPath - Un destino de ubicación definido como una ruta absoluta o una URL externa.

Reglas - pathPattern #

Un patrón que coincide con cada nombre de ruta entrante. Puede ser una ruta absoluta o relativa y admite los siguientes formatos:

  • Coincidencia Exacta: Coincide con la ruta URL exacta.

    • Ejemplo: /exact-path
    • Ejemplo: https://website.com/exact-path
  • Coincidencia por Comodín: Utiliza comodines para hacer coincidir cualquier secuencia de caracteres en la ruta URL. Puede coincidir con segmentos individuales (utilizando *) o múltiples (utilizando **). (Véase Coincidencia de Rutas más abajo).

    • Ejemplo: /intercambiar/* coincidirá con /intercambiar/123 y /intercambiar/abc, capturando sólo el primer segmento después de /intercambiar/.
    • Ejemplo: /categoria/*/objeto/** coincidirá con /categoria/123/objeto/456 y /categoria/abc/objeto/def.
    • Ejemplo: /api/actions/intercambiar/*/confirmar coincidirá con /api/actions/intercambiar/123/confirmar.

Reglas - apiPath #

La ruta de destino de la acción solicitada. Puede definirse como una ruta absoluta o una URL externa.

  • Ejemplo: /api/categoria/*/objeto/*
  • Ejemplo: https://api.ejemplo.com/v1/donar/*
  • Ejemplo: /api/categoria/*/objeto/*
  • Ejemplo: /api/swap/**

Reglas - Parámetros de Consulta #

Los Parámetros de Consulta de la URL original siempre se conservan y se añaden a la URL asignada.

Reglas - Coincidencia de Rutas #

La siguiente tabla muestra la sintaxis de los patrones de coincidencia de ruta:

OperadorCoincidencias
*Un único segmento de ruta, sin incluir los caracteres / separadores de ruta.
**Coincide con cero o más caracteres, incluido cualquier separador de ruta / entre varios segmentos de ruta. Si se incluyen otros operadores, el operador ** debe ser el último.
?Patrón no soportado.

Ejemplos de reglas #

The following example demonstrates an exact match rule to map requests to /buy from your site's root to the exact path /api/buy relative to your site's root:

actions.json
{
  "rules": [
    {
      "pathPattern": "/comprar",
      "apiPath": "/api/comprar"
    }
  ]
}

El siguiente ejemplo utiliza la coincidencia de rutas con comodines para asignar solicitudes a cualquier ruta (excluyendo subdirectorios) en /actions/ desde la raíz de su sitio a una ruta correspondiente en /api/actions/ relativa a la raíz de su sitio:

actions.json
{
  "rules": [
    {
      "pathPattern": "/actions/*",
      "apiPath": "/api/actions/*"
    }
  ]
}

El siguiente ejemplo utiliza una coincidencia de rutas con comodines para asignar solicitudes a cualquier ruta (excluyendo subdirectorios) en /donate/ desde la raíz de su sitio a una ruta absoluta correspondiente https://api.dialect.com/api/v1/donate/ en un sitio externo:

actions.json
{
  "rules": [
    {
      "pathPattern": "/donar/*",
      "apiPath": "https://api.dialect.com/api/v1/donar/*"
    }
  ]
}

El siguiente ejemplo utiliza la coincidencia de rutas con comodines para una regla idempotente que asigna solicitudes a cualquier ruta (incluidos los subdirectorios) en /api/actions/ desde la raíz de su sitio hasta sí mismo:

Info

Las reglas idempotentes permiten a los clientes Blink determinar fácilmente si una ruta admite solicitudes de API de Acción sin tener que tener el prefijo solana-action: ni realizar pruebas adicionales.

actions.json
{
  "rules": [
    {
      "pathPattern": "/api/actions/**",
      "apiPath": "/api/actions/**"
    }
  ]
}

Identidad de la Acción #

La transacción retornada en la respuesta del POST puede incluir una Identidad. Esto permite a los indexadores y plataformas de análisis saber fácilmente y con veracidad qué Proveedor de Acciones (es decir, un servicio) originó la actividad.

La Identidad de la Acción es un par de llaves que se utiliza para firmar un mensaje que tiene un formato especial y se incluye en la transacción mediante una instrucción Memo. Este Mensaje Identificador garantiza la veracidad de la Identidad de la Acción y, por lo tanto, permite atribuir transacciones a un Proveedor de Acciones específico.

No se requiere que el par de llaves firme la transacción en sí. Esto permite que las billeteras y aplicaciones mejoren la capacidad de entrega de las transacciones cuando no hay otras firmas en la transacción devuelta al usuario (consulte transacción de respuesta POST).

Si el caso de uso de un Proveedor de Acciones requiere que su servicio firme previamente la transacción antes de que lo haga el usuario, deben usar este par de llaves como su Identidad de Acción. Esto permitirá incluir una cuenta menos en la transacción, lo que reducirá el tamaño total de las transacciones en 32 bytes.

Mensaje de Identificación de la Acción #

El Mensaje de Identificación de la Acción es una cadena de caracteres UTF-8 separada por dos puntos incluida en una transacción mediante una única instrucción SPL Memo.

protocol:identity:reference:signature
  • protocol: el valor del protocolo que se utiliza (establecido como solana-action según el esquema URL anterior)
  • identity: el valor debe ser la dirección de llave pública codificada en base58 del par de llaves de la Identidad de la Acción
  • reference: el valor debe ser un arreglo de 32 bytes codificado en base58. Estas pueden ser o no llaves públicas, dentro o fuera de la curva, y pueden corresponder o no con cuentas de Solana.
  • signature: firma codificada en base58 creada a partir del par de llaves de la Identidad de la Acción que firma solo el valor de reference.

El valor de reference debe usarse solo una vez y en una sola transacción. A los efectos de asociar transacciones con un Proveedor de Acciones, solo se considera válido el primer uso del valor de reference.

Las transacciones pueden tener múltiples instrucciones Memo. Al realizar un getSignaturesForAddress, el campo memo de los resultados devolverá un mensaje que incluye todos los memos, cada uno separado por punto y coma.

No se deben incluir otros datos en la instrucción Memo del Mensaje de Identificación.

The identity and the reference should be included as read-only, non-signer keys in the transaction on an instruction that is NOT the Identifier Message Memo instruction.

La instrucción Memo del Mensaje de Identificación no debe tener ninguna cuenta. Si se proporciona alguna cuenta, el programa Memo requiere que todas esas cuentas provean una firma válida. A los efectos de identificar acciones, esto restringe la flexibilidad y puede degradar la experiencia del usuario. Por lo tanto, se considera un antipatrón y debe evitarse.

Verificación de identidad de la Acción #

Cualquier transacción que incluya la cuenta identity se puede asociar de manera verificable con el Proveedor de Acciones en un proceso de varios pasos:

  1. Obtenga todas las transacciones para una identity determinada.
  2. Analice y verifique la cadena de caracteres del memo de cada transacción, asegurando que signature es una firma válida para la reference.
  3. Verifique que la transacción sea la primera ocurrencia de reference en la cadena de bloques:
    • If this transaction is the first occurrence, the transaction is considered verified and can be safely attributed to the Action Provider.
    • Si esta transacción NO es la primera ocurrencia, se considera inválida y, por lo tanto, no se atribuye al Proveedor de Acciones.

Debido a que los validadores de Solana indexan las transacciones según las llaves de las cuentas, se puede utilizar el método RPC getSignaturesForAddress para obtener todas las transacciones que incluyen la cuenta que corresponde a identity.

La respuesta de este método del RPC incluye toda la información del campo memo. Si se utilizaron varias instrucciones Memo en la transacción, cada mensaje memo se incluirá en este campo memo y el verificador deberá analizarlo para obtener el Mensaje de Verificación de Identidad.

Estas transacciones deben considerarse inicialmente NO VERIFICADAS. Esto se debe a que no se requiere la identity para firmar la transacción, lo que permite que cualquier transacción incluya esta cuenta sin su firma. Potencialmente inflando de manera artificial el consumo de recursos.

Se debe verificar el Mensaje de Verificación de Identidad para garantizar que la signature fue creada por la identity que firma la reference. Si la verificación de la firma falla, la transacción es inválida y debe atribuirse al Proveedor de Acciones.

Si la verificación de la firma es exitosa, el verificador debe asegurarse de que esta transacción sea la primera aparición reference en la cadena de bloques. Si no es así, la transacción se considera inválida.