Actions et Blinks

Les Actions Solana sont des API conformes aux spécifications qui retournent des transactions sur la blockchain Solana pour être prévisualisées, signées et envoyées via un certain nombre de contextes différents tels que les QR codes, les boutons + widgets et les sites Internet. Les Actions permettent aux développeurs d'intégrer facilement dans leur environnement les choses qu'il est possible de faire dans l'écosystème Solana, ce qui leur permet d'effectuer des transactions blockchain sans avoir à naviguer vers une autre application ou une autre page web.

Les liens blockchain – ou blinks – transforment toute Action Solana en un lien partageable et contenant des métadonnées. Les blinks permettent aux clients sensibles aux Actions (portefeuilles de navigateur, robots) d'afficher des fonctionnalités supplémentaires pour l'utilisateur. Par exemple, sur un site web, un blink peut immédiatement déclencher une prévisualisation de la transaction dans un portefeuille sans passer par une application décentralisée ou encore sur Discord où un robot peut étendre le blink en un ensemble de boutons interactif. La possibilité d'interagir avec la chaîne est ainsi étendue à toute interface web capable d'afficher une URL.

Pour commencer #

Pour commencer rapidement à créer des actions Solana personnalisées :

npm install @solana/actions
  • installez le SDK Solana Actions dans votre application
  • construisez un point de terminaison API pour la requête GET qui retourne les métadonnées de votre Action
  • créez un point de terminaison API qui accepte la requête POST et retourne la transaction signable par l'utilisateur
Info

Regardez ce tutoriel vidéo sur comment construire une Action Solana en utilisant le SDK @solana/actions.

Vous pouvez également trouver le code source pour une Action qui effectue un transfert de SOL natif ici et plusieurs autres exemples d'Actions dans ce dépôt.

Lorsque vous déployez vos Actions Solana personnalisées en production :

Si vous cherchez de l'inspiration pour construire des Actions et des blinks, consultez le dépôt [Awesome Blinks](https://github. om/solana-developers/awesome-blinks) pour des créations de la communauté et même des idées pour de nouvelles créations.

Actions #

La spécification des Actions Solana utilise un ensemble d'API standard pour retourner des transactions pouvant être signées (et éventuellement des messages pouvant être signés) à partir d'une application directement à un utilisateur. Ils sont hébergés sur des URL accessibles au public et sont donc accessibles par leur URL à tout client qui souhaite interagir avec eux.

Info

Vous pouvez considérer les Actions comme un point de terminaison de l'API qui retournera des métadonnées et quelque chose qu'un utilisateur pourra signer (une transaction ou un message d'authentification) avec son portefeuille blockchain.

L'API Actions consiste à effectuer de simples requêtes GET et POST à l'adresse URL d'une Action et à traiter les réponses conformément à l'interface Actions.

  1. la requête GET retourne des métadonnées qui fournissent au client des informations lisibles par l'homme sur les actions disponibles à cette URL, ainsi qu'une liste facultative d'actions associées.
  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.

Exécution et Cycle de vie des Actions #

En pratique, l'interaction avec les Actions ressemble beaucoup à l'interaction avec une API REST classique :

  • un client fait une requête GET initiale à une URL d'Action afin d'obtenir les métadonnées sur les actions disponibles
  • le point de terminaison retourne une réponse qui contient les métadonnées relatives au point de terminaison (comme le titre et l'icône de l'application) et une liste des actions disponibles pour ce point de terminaison
  • l'application client (comme un portefeuille mobile, un chat bot ou un site web) affiche une interface utilisateur permettant à l'utilisateur d'effectuer l'une des actions
  • après que l'utilisateur a sélectionné une action (en cliquant sur un bouton), le client envoie une requête POST au point de terminaison afin d'obtenir la transaction à faire signer par l'utilisateur
  • le portefeuille permet à l'utilisateur de signer la transaction et envoie finalement la transaction à la blockchain pour confirmation

Solana Actions Execution and LifecycleSolana Actions Execution and Lifecycle

Lorsqu'ils reçoivent des transactions d'une URL d'Actions, les clients doivent se charger de la soumission de ces transactions à la blockchain et gérer le cycle de vie de leur état.

Les Actions supportent également un certain niveau d'invalidation avant l'exécution. Les requêtes GET et POST peuvent retourner des métadonnées indiquant si l'action peut être réalisée (comme avec le champ disabled).

Par exemple, s'il existe un point de terminaison Action qui permet de voter sur une proposition de gouvernance d'une DAO dont la fenêtre de vote est fermée, la requête GET initiale peut retourner le message d'erreur "Cette proposition n'est plus soumise au vote" et les boutons "Voter Oui" et "Voter Non" comme étant "désactivés".

Les Blinks (liens blockchain) sont des applications client qui analysent les API d'Action et construisent des interfaces utilisateur permettant d'interagir avec les Actions et de les exécuter.

Les applications client qui prennent en charge les blinks ne font que détecter les URL compatibles avec les Actions, les analyser et permettre aux utilisateurs d'interagir avec eux au moyen d'interfaces utilisateur standardisées.

Info

Toute application client qui analyse en profondeur une API d'Actions pour en construire une interface complète est un blink. Par conséquent, tous les clients qui utilisent des API d'Actions ne sont pas forcément des blinks.

Une URL blink décrit une application client qui permet à un utilisateur d'effectuer le cycle de vie complet de l'exécution d'une Action, y compris la signature avec son portefeuille.

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

Pour qu'une application client devienne un blink :

  • L'URL blink doit contenir un paramètre de requête action dont la valeur est une URL d'Action encodée. Cette valeur doit être encodée pour ne pas entrer en conflit avec d'autres paramètres du protocole.

  • L'application client doit décoder le paramètre de requête action et analyser le lien de l'API Action fourni (voir schéma d'URL d'Action).

  • Le client doit afficher une interface utilisateur riche qui permet à un utilisateur d'effectuer le cycle de vie complet de l'exécution d'une Action, y compris la signature avec son portefeuille.

Info

Toutes les applications client blink (ex. sites Web ou dApps) ne prendront pas en charge toutes les Actions. En effet, les développeurs d'applications peuvent choisir les Actions qu'ils souhaitent prendre en charge dans leurs interfaces blink.

L'exemple suivant montre une URL blink valide avec une valeur action égale à solana-action:https://actions.alice.com/donate qui est encodée dans l'URL :

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

Les blinks peuvent être liés à des Actions d'au moins 3 façons :

  1. Partage d'une URL d'Action explicite : solana-action:https://actions.alice.com/donate

    Dans ce cas, seuls les clients pris en charge peuvent rendre le blink. Il n'y aura pas de prévisualisation de lien de remplacement, ni de site pouvant être visité en dehors du client non supporté.

  2. Partage d'un lien vers un site web lié à une API Actions via un fichier actions.json présent à la racine du domaine du site web.

    Par exemple, https://alice.com/actions.json associe https://alice.com/donate, URL d'un site web où les utilisateurs peuvent faire un don à Alice, à l'URL de l'API https://actions.alice.com/donate, où sont hébergées les Actions permettant de faire un don à Alice.

  3. Intégration d'une URL d'Action dans l'URL d'un site "interstitiel" qui comprend comment analyser les Actions.

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

Les clients qui prennent en charge les blinks doivent être en mesure d'accepter l'un des formats ci-dessus et d'afficher correctement une interface pour faciliter l'exécution de l'action directement dans le client.

Pour les clients qui ne prennent pas en charge les blinks, il doit y avoir un site web sous-jacent (le navigateur devenant ainsi la solution de remplacement universelle).

Si un utilisateur appuie sur un élément du client qui n'est pas un bouton d'action ou un champ de saisie de texte, il doit être redirigé vers le site sous-jacent.

Bien que les Actions Solana et les blinks soient un protocole/une spécification ne nécessitant pas de permission (permissionless), les applications client et les portefeuilles sont toujours nécessaires pour faciliter la signature des transactions par les utilisateurs.

Info

Utilisez l'Inspecteur de Blinks pour inspecter, déboguer et tester vos blinks et actions directement dans votre navigateur. Vous pouvez afficher les blocs de réponse GET et POST, les en-têtes de réponse et tester toutes les entrées vers chacune de vos Actions liées.

Chaque application client ou portefeuille peut avoir des exigences différentes en ce qui concerne les points de terminaison d'Action que ses clients déploieront automatiquement et afficheront immédiatement à leurs utilisateurs sur les plateformes de médias sociaux.

Par exemple, certains clients peuvent fonctionner selon une approche de "liste d'autorisation" qui peut nécessiter une vérification avant que le client ne déploie une Action pour des utilisateurs tel que le Registre d'Actions de Dialect (détaillé ci-dessous).

Tous les blinks seront toujours affichés et permettront de signer sur le site interstitiel de blinks dial.to de Dialect avec leur statut d'enregistrement affiché dans le blink.

Registre d'Actions de Dialect #

En tant que bien public pour l'écosystème Solana, Dialect maintient un registre public - avec l'aide de la Fondation Solana et d'autres membres de la communauté - des liens blockchain qui ont été pré-vérifiés à partir de sources connues. Dès le lancement, seules les Actions enregistrées dans le registre de Dialect se déploieront dans le fil Twitter lorsqu'elles seront publiées.

Les applications client et les portefeuilles peuvent librement choisir d'utiliser ce registre public ou une autre solution pour garantir la sécurité des utilisateurs. S'il n'est pas vérifié par le registre Dialect, le lien blockchain ne sera pas touché par le client blink et sera rendu comme une URL classique.

Les développeurs peuvent demander à être vérifiés par Dialect ici : dial.to/register

Spécification #

La spécification des Actions Solana consiste en des sections clés qui font partie d'un flux d'interaction de requête/réponse :

Chacune de ces requêtes est effectuée par le client d'Action (par exemple application de portefeuille, extension de navigateur, dApp, site web, etc.) afin de recueillir des métadonnées spécifiques pour les interfaces utilisateur enrichies et pour faciliter la saisie de l'utilisateur dans l'API d'Actions.

Chacune des réponses est élaborée par une application (site web, serveur backend, etc.) et renvoyée au client d'Action. Finalement, elle fournit une transaction ou un message pouvant être signé par un portefeuille et invite l'utilisateur à l'approuver, le ou la signer et à l'envoyer à la blockchain.

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.

Schéma d'URL #

Une URL d'Action Solana décrit une requête interactive pour une transaction ou un message Solana pouvant être signé, en utilisant le protocole solana-action.

La requête est interactive car les paramètres de l'URL sont utilisés par un client pour effectuer une série de requêtes HTTP standardisées afin de composer une transaction ou un message pouvant être signé par l'utilisateur à l'aide de son portefeuille.

solana-action:<link>
  • Un seul champ link est requis comme chemin d'accès. La valeur doit être une URL HTTPS absolue encodée de manière conditionnelle.

  • Si l'URL contient des paramètres de requête, elle doit être encodée. L'encodage de la valeur permet d'éviter tout conflit avec les paramètres du protocole Actions qui peuvent être ajoutés via la spécification du protocole.

  • Si l'URL ne contient pas de paramètres de requête, elle ne doit pas être encodée. Il en résulte une URL plus courte et un QR code moins dense.

Dans les deux cas, les clients doivent décoder la valeur. Cela n'a aucun effet si la valeur n'est pas encodée. Si la valeur décodée n'est pas une URL HTTPS absolue, le portefeuille doit la rejeter comme étant malformée.

Réponse OPTIONS #

Afin de permettre le Cross-Origin Resource Sharing (CORS) dans les clients Actions (y compris les blinks), tous les points de terminaison Action doivent répondre aux requêtes HTTP par la méthode OPTIONS avec des en-têtes valides ce qui permettra aux clients de passer les contrôles CORS pour toutes les requêtes ultérieures provenant du même domaine d'origine.

Un client Actions peut effectuer des demandes de "contrôle préalable" au point de terminaison de l'URL d'Action afin de vérifier si la demande GET ultérieure à l'URL d'Action passera toutes les vérifications CORS. Ces contrôles préalables CORS sont effectués à l'aide de la méthode HTTP OPTIONS et doivent répondre avec tous les en-têtes HTTP nécessaires pour permettre aux clients Action (comme les blinks) d'effectuer correctement toutes les requêtes ultérieures à partir de leur domaine d'origine.

Au minimum, les en-têtes HTTP requis sont les suivants :

  • Access-Control-Allow-Origin avec une valeur de *
    • cela permet de s'assurer que tous les clients Action peuvent passer les contrôles CORS en toute sécurité afin d'effectuer toutes les requêtes nécessaires
  • Access-Control-Allow-Methods avec une valeur de GET,POST,PUT,OPTIONS
    • cela permet de s'assurer que toutes les méthodes de requête HTTP requises sont prises en charge par les Actions
  • Access-Control-Allow-Headers avec une valeur minimum de Content-Type, Authorization, Content-Encoding, Accept-Encoding

Pour des raisons de simplicité, les développeurs devraient envisager de retourner la même réponse et les mêmes en-têtes aux requêtes OPTIONS que leur réponse GET.

Cross-Origin headers for actions.json

La réponse du fichier actions.json doit également retourner des en-têtes Cross-Origin valides pour les requêtes GET et OPTIONS, en particulier la valeur d'en-tête Access-Control-Allow-Origin de *.

Voir actions.json ci-dessous pour plus de détails.

Requête GET #

Le client d'Action (par exemple portefeuille, extension de navigateur, etc.) doit effectuer une requête JSON HTTP GET au point de terminaison de l'URL d'Action.

  • La requête ne doit pas identifier le portefeuille ou l'utilisateur.
  • Le client doit faire la requête avec un en-tête Accept-Encoding.
  • Le client doit afficher le domaine de l'URL lors de la requête.

Réponse GET #

Le point de terminaison de l'URL d'Action (par exemple application ou serveur backend) doit répondre par une réponse JSON HTTP OK (avec une charge utile valide dans le corps) ou par une erreur HTTP appropriée.

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.

Corps de la réponse GET #

Une réponse GET avec une réponse JSON HTTP OK doit inclure dans le corps une charge utile conforme à la spécification de l'interface :

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 - La valeur doit être une URL HTTP ou HTTPS absolue d'une image d'icône. Le fichier doit être une image SVG, PNG ou WebP, sinon le client/le portefeuille doit le rejeter comme étant malformé.

  • title - La valeur doit être une chaîne UTF-8 qui représente la source de la requête d'action. Il peut s'agir, par exemple, du nom d'une marque, d'un magasin, d'une application ou de la personne qui fait la requête.

  • description - La valeur doit être une chaîne UTF-8 qui fournit des informations sur l'action. La description doit être affichée à l'utilisateur.

  • label - La valeur doit être une chaîne UTF-8 qui sera affichée sur un bouton sur lequel l'utilisateur pourra cliquer. Toutes les étiquettes (labels) ne doivent pas dépasser 5 mots et doivent commencer par un verbe pour renforcer l'action que vous voulez que l'utilisateur fasse. Par exemple, "Mint NFT", "Votez Oui" ou "Staker 1 SOL".

  • disabled - La valeur doit être un booléen pour représenter l'état désactivé du bouton affiché (qui affiche la chaîne label). Si aucune valeur n'est fournie, la valeur par défaut de disabled est false (c'est-à-dire activée par défaut). Par exemple, si le point de terminaison de l'action concerne un vote de gouvernance qui a été clôturé, définissez disabled=true et le label pourrait être "Vote Clôturé".

  • error - Une indication d'erreur facultative pour les erreurs non fatales. Si l'indication est présente, le client doit l'afficher à l'utilisateur. 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 tableau optionnel d'actions associées au point de terminaison. Une interface utilisateur doit présenter aux utilisateurs chacune des actions listées et il doit leur être demandé de n'en effectuer qu'une seule. Par exemple, un point de terminaison d'action de vote de gouvernance peut renvoyer trois options à l'utilisateur : "Voter Oui", "Voter Non" et "S'abstenir de Voter".

    • Si aucun links.actions n'est fourni, le client doit afficher un seul bouton en utilisant la chaîne label de la racine et envoyer une requête POST au même point de terminaison de l'URL d'action que la requête GET initiale.

    • Si des links.actions sont fournis, le client ne doit afficher les boutons et les champs de saisie qu'en fonction des éléments listés dans le champ links.actions. Le client ne doit donc pas afficher de bouton pour le contenu du label de la racine.

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.

Exemple de réponse GET #

L'exemple de réponse suivant fournit une action "root" unique qui doit être présentée à l'utilisateur sous la forme d'un bouton unique intitulé "Claim Access Token" (Réclamer un Jeton d'Accès) :

{
  "title": "HackerHouse Events",
  "icon": "<url-to-image>",
  "description": "Claim your Hackerhouse access token.",
  "label": "Claim Access Token" // button text
}

L'exemple de réponse suivant fournit 3 liens d'action associée qui permettent à l'utilisateur de cliquer sur l'un des 3 boutons pour voter lors d'une proposition de DAO :

{
  "title": "Realms DAO Platform",
  "icon": "<url-to-image>",
  "description": "Vote on DAO governance proposals #1234.",
  "label": "Vote",
  "links": {
    "actions": [
      {
        "label": "Vote Yes", // button text
        "href": "/api/proposal/1234/vote?choice=yes"
      },
      {
        "label": "Vote No", // button text
        "href": "/api/proposal/1234/vote?choice=no"
      },
      {
        "label": "Abstain from Vote", // button text
        "href": "/api/proposal/1234/vote?choice=abstain"
      }
    ]
  }
}

Exemple de réponse GET avec Paramètres #

Les exemples de réponse suivants montrent comment accepter une saisie de texte de la part de l'utilisateur (via des parameters (paramètres)) et inclure cette saisie dans le point de terminaison de la requête POST finale (via le champ href d'une LinkedAction) :

L'exemple de réponse suivant fournit à l'utilisateur 3 actions associées pour staker des SOL : un bouton libellé "Staker 1 SOL", un autre bouton libellé "Staker 5 SOL" et un champ de saisie de texte qui permet à l'utilisateur d'entrer une valeur spécifique de "amount" (montant) qui sera envoyée à l'API d'Action :

{
  "title": "Stake-o-matic",
  "icon": "<url-to-image>",
  "description": "Stake SOL to help secure the Solana network.",
  "label": "Stake SOL", // not displayed since `links.actions` are provided
  "links": {
    "actions": [
      {
        "label": "Stake 1 SOL", // button text
        "href": "/api/stake?amount=1"
        // no `parameters` therefore not a text input field
      },
      {
        "label": "Stake 5 SOL", // button text
        "href": "/api/stake?amount=5"
        // no `parameters` therefore not a text input field
      },
      {
        "label": "Stake", // button text
        "href": "/api/stake?amount={amount}",
        "parameters": [
          {
            "name": "amount", // field name
            "label": "SOL amount" // text input placeholder
          }
        ]
      }
    ]
  }
}

L'exemple de réponse suivant fournit un champ de saisie unique permettant à l'utilisateur d'entrer un amount (montant) qui est envoyé avec la requête POST (soit en tant que paramètre de requête, soit en tant que sous-chemin) :

{
  "icon": "<url-to-image>",
  "label": "Donate SOL",
  "title": "Donate to GoodCause Charity",
  "description": "Help support this charity by donating SOL.",
  "links": {
    "actions": [
      {
        "label": "Donate", // button text
        "href": "/api/donate/{amount}", // or /api/donate?amount={amount}
        "parameters": [
          // {amount} input field
          {
            "name": "amount", // input field name
            "label": "SOL amount" // text input placeholder
          }
        ]
      }
    ]
  }
}

Requête POST #

Le client doit faire une requête JSON HTTP POST à l'URL de l'action avec dans le corps une charge utile contenant :

{
  "account": "<account>"
}
  • account - La valeur doit être la clé publique encodée en base58 d'un compte qui peut signer la transaction.

Le client doit faire la requête avec un en-tête Accept-Encoding et l'application peut répondre avec un en-tête Content-Encoding pour la compression HTTP.

Le client doit afficher le domaine de l'URL de l'action lorsque la requête est effectuée. Si une requête GET a été faite, le client doit également afficher le title et afficher l'image icon de cette réponse GET.

Réponse POST #

Le point de terminaison POST de l'Action doit répondre par une réponse JSON HTTP OK (avec une charge utile valide dans le corps) ou par une erreur HTTP appropriée.

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.

Corps de la réponse POST #

Une réponse POST avec une réponse JSON HTTP OK doit inclure dans le corps de la charge utile :

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 - La valeur doit être une chaîne UTF-8 décrivant la nature de la transaction incluse dans la réponse. Le client doit afficher cette valeur à l'utilisateur. Il peut s'agir, par exemple, du nom d'un article acheté, d'une réduction appliquée lors d'un achat ou d'une note de remerciement.

  • 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.

  • Le client et l'application doivent autoriser des champs supplémentaires dans le corps de la requête et dans le corps de la réponse qui pourront être ajoutés par de futures mises à jour de la spécification.

Info

L'application peut répondre par une transaction partiellement ou totalement signée. Le client et le portefeuille doivent valider la transaction comme étant non fiable.

Réponse POST - Transaction #

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.
  • Le client doit sérialiser et désérialiser la transaction avant de la signer. Cela permet d'assurer un ordre cohérent des clés de compte et constitue une solution de contournement à ce problème.

Si la transaction a été partiellement signée :

  • The client must NOT alter the feePayer or recentBlockhash as this would invalidate any existing signatures.
  • Le client doit vérifier les signatures existantes, et si l'une d'entre elles n'est pas valide, il doit rejeter la transaction comme étant malformée.

Le client ne doit signer la transaction qu'avec le account indiqué dans la requête, et ne doit le faire que si une signature pour le account indiqué dans la requête est attendue.

Si une signature autre que celle du account présent dans la requête est attendue, le client doit rejeter la transaction comme étant malveillante.

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 #

L'objectif du fichier actions.json est de permettre à une application d'indiquer aux clients quelles URL de sites web prennent en charge les Actions Solana et de fournir un mappage qui peut être utilisé pour effectuer des requêtes GET à un serveur API d'Actions.

Cross-Origin headers are required

La réponse du fichier actions.json doit également retourner des en-têtes Cross-Origin valides pour les requêtes GET et OPTIONS, en particulier la valeur d'en-tête Access-Control-Allow-Origin de *.

Voir Réponse OPTIONS ci-dessus pour plus de détails.

Le fichier actions.json doit être stocké et universellement accessible à la racine du domaine.

Par exemple, si votre application web est déployée à l'adresse my-site.com alors le fichier actions.json doit être accessible à l'adresse https://my-site.com/actions.json. Ce fichier doit également être Cross-Origin accessible via n'importe quel navigateur en ayant une valeur d'en-tête Access-Control-Allow-Origin de *.

Règles #

Le champ rules (règles) permet à l'application de mapper un ensemble de chemins d'accès relatifs d'un site web à un ensemble d'autres chemins d'accès.

Type: Array de ActionRuleObject.

ActionRuleObject
interface ActionRuleObject {
  /** relative (preferred) or absolute path to perform the rule mapping from */
  pathPattern: string;
  /** relative (preferred) or absolute path that supports Action requests */
  apiPath: string;
}
  • pathPattern - Un modèle qui correspond à chaque chemin d'accès entrant.

  • apiPath - Une destination définie comme un chemin d'accès absolu ou une URL externe.

Règles - pathPattern #

Un modèle qui correspond à chaque chemin d'accès entrant. Il peut s'agir d'un chemin absolu ou relatif et prend en charge les formats suivants :

  • Exact Match (Correspondance Exacte) : Correspond au chemin d'accès exact de l'URL.

    • Exemple : /exact-path
    • Exemple : https://website.com/exact-path
  • Wildcard Match (Correspondance avec des Caractères Génériques) : Utilise des caractères génériques pour correspondre à n'importe quelle séquence de caractères dans le chemin d'accès de l'URL. Il peut s'agir d'un seul segment (en utilisant *) ou de plusieurs segments (en utilisant **). (voir Correspondance des Chemins ci-dessous).

    • Exemple : /trade/* correspondra à /trade/123 et à /trade/abc, capturant seulement le premier segment après /trade/.
    • Exemple : /category/*/item/** correspondra à /category/123/item/456 et à /category/abc/item/def.
    • Exemple : /api/actions/trade/*/confirm correspondra à /api/actions/trade/123/confirm.

Règles - apiPath #

Le chemin de destination de la requête d'action. Il peut être défini comme un nom de chemin absolu ou une URL externe.

  • Exemple : /api/exact-path
  • Exemple : https://api.example.com/v1/donate/*
  • Exemple : /api/category/*/item/*
  • Exemple : /api/swap/**

Règles - Paramètres de Requête #

Les paramètres de requête de l'URL d'origine sont toujours conservés et ajoutés à l'URL mappée.

Règles - Correspondance des Chemins #

Le tableau suivant présente la syntaxe des modèles de correspondance de chemin :

OpérateurCorrespondance
*Un seul segment de chemin sans inclure le séparateur de chemin / caractères environnant.
**Correspond à zéro ou plusieurs caractères, y compris tout séparateur de chemin / caractères entre plusieurs segments de chemin. Si d'autres opérateurs sont inclus, l'opérateur ** doit être le dernier opérateur.
?Modèle non pris en charge.

Exemples de règles #

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": "/buy",
      "apiPath": "/api/buy"
    }
  ]
}

L'exemple suivant utilise la correspondance des chemins d'accès avec des caractères génériques pour faire correspondre les requêtes à n'importe quel chemin d'accès (en excluant les sous-répertoires) sous /actions/ depuis la racine de votre site à un chemin d'accès correspondant sous /api/actions/ relatif à la racine de votre site :

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

L'exemple suivant utilise la correspondance des chemins d'accès avec des caractères génériques pour faire correspondre les requêtes à n'importe quel chemin d'accès (en excluant les sous-répertoires) sous /donate/ depuis la racine de votre site à un chemin d'accès absolu correspondant, https://api.dialect.com/api/v1/donate/\`, sur un site externe :

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

L'exemple suivant utilise la correspondance des chemins d'accès avec des caractères génériques pour une règle idempotente pour faire correspondre les requêtes à n'importe quel chemin d'accès (y compris les sous-répertoires) sous /api/actions/ de la racine de votre site à lui-même :

Info

Les règles idempotentes permettent aux clients blink de déterminer plus facilement si un chemin donné prend en charge les requêtes de l'API Action sans avoir à être préfixé par l'URI solana-action: ou à effectuer des tests de réponse supplémentaires.

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

Identité d'Action #

Les points de terminaison d'Action peuvent inclure une Action Identity (Identité d'Action) dans les transactions qui sont retournées dans leur réponse POST que l'utilisateur doit signer. Cela permet aux indexeurs et aux plateformes d'analyse d'attribuer facilement et de manière vérifiable l'activité sur la chaîne à un Action Provider (Fournisseur d'Action) en particulier (c'est-à-dire un service).

L'Identité d'Action est une paire de clés utilisée pour signer un message spécifiquement formaté qui est inclus dans la transaction à l'aide d'une instruction Memo. Ce Identifier Message (Message d'Identification) peut être attribué de manière vérifiable à une Identité d'Action en particulier, et donc attribuer des transactions à un Fournisseur d'Action en particulier.

La paire de clés n'est pas nécessaire pour signer la transaction. Cela permet aux portefeuilles et aux applications d'améliorer la délivrabilité des transactions lorsqu'aucune autre signature ne figure dans la transaction retournée à l'utilisateur (voir Réponse POST - Transaction).

Si le cas d'utilisation d'un Fournisseur d'Action nécessite que ses services backend pré-signent la transaction avant l'utilisateur, il doit utiliser cette paire de clés comme Identité d'Action. Cela permettra d'inclure un compte de moins dans la transaction, réduisant la taille totale de la transaction de 32 octets.

Message d'Identification d'Action #

Le Message d'Identification d'Action est une chaîne UTF-8 séparée par deux points, incluse dans une transaction à l'aide d'une seule instruction SPL Memo.

protocol:identity:reference:signature
  • protocol - La valeur du protocole utilisé (fixé à solana-action selon le Schéma d'URL ci-dessus)
  • identity - La valeur doit être l'adresse de la clé publique encodée en base58 de la paire de clés de l'Identité d'Action
  • reference - La valeur doit être un tableau de 32 octets encodé en base58. Il peut s'agir ou non de clés publiques, sur ou en dehors de la courbe, et correspondre ou non à des comptes sur Solana.
  • signature - signature encodée en base58 créée à partir de la paire de clés de l'Identité d'Action et signant uniquement la valeur reference.

La valeur reference ne doit être utilisée qu'une seule fois et dans une seule transaction. Afin d'associer des transactions à un Fournisseur d'Actions, seule la première utilisation de la valeur reference est considérée comme valide.

Les transactions peuvent avoir plusieurs instructions Mémo. Lors de l'exécution d'un getSignaturesForAddress, le champ memo résultant retournera le message de chaque instruction memo sous la forme d'une chaîne unique, chaque instruction étant séparée par un point-virgule.

Aucune autre donnée ne doit être incluse dans l'instruction Memo du Message d'Identification.

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.

L'instruction Memo du Message d'Identification doit avoir aucun compte fourni. Si des comptes sont fournis, le programme Memo exige que ces comptes soient des signataires valides. Pour identifier les actions, cela limite la flexibilité et peut dégrader l'expérience utilisateur. C'est pourquoi il est considéré comme un anti-modèle et doit être évité.

Vérification de l'Identité d'Action #

Toute transaction qui inclut le compte identity peut être associée de manière vérifiable au Fournisseur d'Actions grâce à un processus en plusieurs étapes :

  1. Obtenir toutes les transactions pour une identity donnée.
  2. Analyser et vérifier la chaîne mémo de chaque transaction, en s'assurant que la signature est valide pour la reference stockée.
  3. Vérifier que la transaction en question est la première occurrence sur la chaine de la reference :
    • If this transaction is the first occurrence, the transaction is considered verified and can be safely attributed to the Action Provider.
    • Si cette transaction n'est PAS la première occurrence, elle est considérée comme invalide et n'est donc pas attribuée au Fournisseur d'Actions.

Comme les validateurs Solana indexent les transactions selon les clés de compte, la méthode RPC getSignaturesForAddress peut être utilisée pour localiser toutes les transactions incluant le compte identity.

La réponse de cette méthode RPC inclut toutes les données Memo dans le champ memo. Si plusieurs instructions Memo ont été utilisées dans la transaction, chaque message memo sera inclus dans le champ memo et devra être analysé en conséquence par le vérificateur afin d'obtenir le Identity Verification Message (Message de Vérification d'Identité).

Ces transactions doivent être initialement considérées comme NON VÉRIFIÉES. Cela est dû au fait que l'identity n'est pas requise pour signer la transaction, ce qui permet à toute transaction d'inclure ce compte en tant que non-signataire. Cela peut ainsi potentiellement gonfler artificiellement le nombre d'attributions et d'utilisations.

Le Message de Vérification d'Identité doit être vérifié pour s'assurer que la signature a été créée par l'identity qui signe la reference. Si la vérification de la signature échoue, la transaction n'est pas valide et doit être attribuée au Fournisseur d'Action.

Si la vérification de la signature réussie, le vérificateur doit s'assurer que cette transaction est la première occurrence sur la chaîne de la reference. Si ce n'est pas le cas, la transaction est considérée comme invalide.