Actions e Blinks

Solana Actions são APIs que seguem especificações e retornam transações na blockchain da Solana para serem visualizadas, assinadas e enviadas em diversos contextos, incluindo QR codes, botões, widgets e sites na internet. Actions facilitam a vida dos desenvolvedores ao permitir que eles integrem as funcionalidades do ecossistema Solana diretamente em seus ambientes, possibilitando a realização de transações blockchain sem precisar sair do aplicativo ou página atual.

Links de blockchain – ou blinks – transformam qualquer Solana Action em um link compartilhável e rico em metadados. Blinks permitem que clients que entendem Actions (como carteiras de extensão de navegador e bots) exibam capacidades adicionais para o usuário. Em um site, um blink pode imediatamente acionar uma pré-visualização de transação em uma carteira sem ir para um aplicativo descentralizado; no Discord, um bot pode expandir o blink em um conjunto interativo de botões. Isso leva a capacidade de interação on-chain para qualquer superfície web capaz de exibir uma URL.

Get Started #

To quickly get started with creating custom Solana Actions:

npm install @solana/actions
  • install the Solana Actions SDK in your application
  • build an API endpoint for the GET request that returns the metadata about your Action
  • create an API endpoint that accepts the POST request and returns the signable transaction for the user
Info

Checkout this video tutorial on how to build a Solana Action using the @solana/actions SDK.

You can also find the source code for an Action that performs a native SOL transfer here and several other example Actions in this repo.

When deploying your custom Solana Actions to production:

If you are looking for inspiration around building Actions and blinks, checkout the Awesome Blinks repository for some community creations and even ideas for new ones.

Action #

A especificação Solana Actions usa um conjunto de APIs padrão para entregar transações assináveis (e eventualmente mensagens assináveis) de um aplicativo diretamente para um usuário. Elas são hospedadas em URLs acessíveis publicamente e, portanto, acessíveis por qualquer client via URL para interação.

Info

Você pode pensar em Actions como um endpoint de API que retornará metadados e algo para o usuário assinar (uma transação ou uma mensagem de autenticação) com sua carteira blockchain.

A API Actions consiste em fazer requisições simples GET e POST para o endpoint da URL de uma Action e tratar as respostas que seguem a interface de Actions.

  1. A requisição GET retorna metadados que fornecem informações legíveis para o client sobre quais ações estão disponíveis nessa URL, e uma lista opcional de ações 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.

Execução e Ciclo de Vida de uma Action #

Na prática, interagir com Actions é bem parecido com interagir com uma API REST típica:

  • um client faz a requisição inicial GET para uma URL Action, para obter metadados sobre as Actions disponíveis
  • o endpoint retorna uma resposta que inclui metadados sobre o endpoint (como o título e o ícone do aplicativo) e uma lista das ações disponíveis para esse endpoint
  • o aplicativo client (como uma carteira móvel, bot de chat ou site) exibe uma interface para o usuário realizar uma das ações
  • depois que o usuário seleciona uma ação (clicando em um botão), o client faz uma requisição POST para o endpoint obter a transação que o usuário deve assinar
  • a carteira facilita a assinatura da transação pelo usuário e, finalmente, envia a transação para a blockchain para confirmação

Execução e Ciclo de Vida da Solana ActionsExecução e Ciclo de Vida da Solana Actions

Ao receber transações de uma URL de Actions, os clients devem gerenciar o envio dessas transações para a blockchain e gerenciar seu ciclo de vida de estado.

Actions também suportam algum nível de invalidação antes da execução. As requisições GET e POST podem retornar alguns metadados que informam se a ação pode ser realizada (como com o campo disabled).

Por exemplo, se houver um endpoint de Action que facilita a votação em uma proposta de governança de DAO cujo período de votação já encerrou, a requisição GET inicial pode retornar a mensagem de erro "Esta proposta não está mais aberta para votação" e os botões "Votar Sim" e "Votar Não" como "desabilitados".

Blinks (links de blockchain) são aplicações client que inspecionam APIs de Action e constroem interfaces de usuário para interagir e executar Actions.

Aplicações client que suportam blinks simplesmente detectam URLs compatíveis com Actions, as interpretam e permitem que os usuários interajam com elas em interfaces de usuário padronizadas.

Info

Qualquer aplicação client que inspecione completamente uma API de Actions para construir uma interface completa para ela é um blink. Portanto, nem todos os clients que consomem APIs Actions são blinks.

Uma URL de blink descreve uma aplicação client que permite ao usuário completar todo o ciclo de vida da execução de uma Action, incluindo assinar com sua carteira.

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

Para qualquer aplicação client se tornar um blink:

  • A URL blink deve conter um parâmetro de consulta action cujo valor é uma URL de Action codificada (URL da ação](#url-scheme). Este valor deve ser URL-encoded para não conflitar com outros parâmetros de protocolo.

  • A aplicação client deve URL-decode o parâmetro de consulta action e analisar o link da API Action fornecido (veja esquema de URL Action).

  • O client deve renderizar uma interface de usuário rica que permita ao usuário completar o ciclo completo de execução de uma Action(#action-execution-and-lifecycle), incluindo a assinatura com sua carteira.

Info

Nem todas as aplicações client de blink (por exemplo, sites ou dApps) suportarão todas as Actions. Os desenvolvedores de aplicativos podem escolher quais Actions desejam suportar dentro de suas interfaces de blink.

O exemplo a seguir demonstra uma URL de blink válida com um valor action de solana-action:https://actions.alice.com/donate que está codificado na URL:

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

Blinks podem ser vinculados aos Actions de pelo menos 3 maneiras:

  1. Compartilhando uma URL Action explícita: solana-action:https://actions.alice.com/donate

    Nesse caso, apenas clients compatíveis podem renderizar o blink. Não haverá uma pré-visualização do link ou um site que possa ser visitado fora do client não compatível.

  2. Compartilhando um link para um site que esteja vinculado a uma API de Actions por meio de um arquivo actions.json file na raiz do domínio do site.

    Por exemplo, https://alice.com/actions.json mapeia https://alice.com/donate, uma URL de site onde os usuários podem doar para Alice, para a URL da API https://actions.alice.com/donate, onde Actions para doações a Alice estão hospedadas.

  3. Incorporando uma URL de Action em uma URL de site "intermediário" que entende como analisar Actions.

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

Clients que suportam blinks devem ser capazes de usar qualquer um dos formatos acima e renderizar corretamente uma interface para facilitar a execução da ação diretamente no client.

Para clients que não suportam blinks, deve haver um site subjacente (fazendo com que o navegador se torne a solução universal).

Se um usuário tocar em qualquer lugar do client que não seja um botão de ação ou campo de entrada de texto, ele deve ser levado ao site subjacente.

Embora Solana Actions e blinks sejam um protocolo/especificação sem permissões, aplicações client e carteiras ainda são necessárias para facilitar a assinatura da transação pelos usuários.

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 exemplo, alguns clients podem operar com uma abordagem de "lista de permissão" que pode exigir verificação antes de seus clients expandirem uma Action para os usuários, como o Registro de Actions da Dialect (detalhado abaixo).

Todos os blinks ainda serão renderizados e permitirão a assinatura no dial.to site intersticial de blinks da Dialect, com seu status de registro exibido no blink.

Registro de Actions da Dialect #

Como um bem público para o ecossistema Solana, a Dialect mantém um registro público — juntamente com a ajuda da Solana Foundation e outros membros da comunidade — de links de blockchain que foram pré-verificados de fontes conhecidas. Desde o lançamento, apenas Actions registradas no registro da Dialect serão expandidas no feed do Twitter quando postadas.

Aplicações client e carteiras podem escolher livremente usar esse registro público ou outra solução para ajudar a garantir a segurança e a proteção dos usuários. Se não for verificado pelo registro da Dialect, o link de blockchain não será tocado pelo client blink e será renderizado como uma URL típica.

Os desenvolvedores podem se inscrever para serem verificados pela Dialect aqui: dial.to/register

Especificação #

A especificação do Solana Actions é composta por seções principais que fazem parte do fluxo de interação de requisição/resposta:

Cada uma dessas requisições é feita pelo Action client (por exemplo, aplicativo de carteira, extensão de navegador, dApp, site, etc) para coletar metadados específicos e facilitar a interação do usuário com a API de Actions.

Cada uma das respostas é elaborada por uma aplicação (por exemplo, site, backend de servidor, etc) e retornada ao Action client. O objetivo final é fornecer uma transação ou mensagem assinável para que a carteira do usuário solicite a aprovação, assinatura e envio para a 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.

Esquema da URL #

Uma URL de Action da Solana descreve uma requisição interativa para uma transação ou mensagem assinável da Solana usando o protocolo solana-action.

A requisição é interativa porque os parâmetros na URL são usados por um client para fazer uma série de requisições HTTP padronizadas para compor uma transação ou mensagem assinável para o usuário assinar com sua carteira.

solana-action:<link>
  • Um único campo de link é necessário como o caminho. O valor deve ser uma URL HTTPS absoluta condicionalmente codificada na URL-encoded.

  • Se a URL contiver parâmetros de consulta, ela deve ser codificada. Codificar a URL evita conflitos com quaisquer parâmetros de protocolo de Actions, que podem ser adicionados via especificação de protocolo.

  • Se a URL não contiver parâmetros de consulta, ela não deve ser codificada. Isso produz uma URL mais curta e um código QR menos denso.

Em ambos os casos, os clients devem decodificar o valor da URL-decode. Isso não tem efeito se o valor não estiver como URL codificado. Se o valor decodificado não for uma URL HTTPS absoluta, a carteira deve rejeitá-lo como malformado.

Resposta OPTIONS #

Para permitir o Cross-Origin Resource Sharing (CORS) dentro dos clients de Actions (incluindo blinks), todos os endpoints de Action devem responder a requisições HTTP para o método OPTIONS com cabeçalhos válidos que permitirão que os clients passem nas verificações de CORS para todas as requisições subsequentes do mesmo domínio de origem.

Um client de Actions pode realizar requisições de "preflight" para o endpoint da URL de Action a fim de verificar se a requisição GET subsequente para a URL de Action passará em todas as verificações de CORS. Essas verificações de preflight CORS são feitas usando o método HTTP OPTIONS e devem responder com todos os cabeçalhos HTTP necessários que permitirão que os clients de Actions (como blinks) façam adequadamente todas as requisições subsequentes do seu domínio de origem.

No mínimo, os cabeçalhos HTTP necessários incluem:

  • Access-Control-Allow-Origin com um valor de *
    • isso garante que todos os clients de Actions possam passar nas verificações de CORS para fazer todas as requisições necessárias
  • Access-Control-Allow-Methods com um valor de GET,POST,PUT,OPTIONS
    • garante que todos os métodos de requisição HTTP necessários sejam suportados para Actions
  • Access-Control-Allow-Headers com um valor mínimo de Content-Type, Authorization, Content-Encoding, Accept-Encoding

Para simplicidade, os desenvolvedores devem considerar retornar a mesma resposta e cabeçalhos para as requisições OPTIONS que para as respostas GET response.

Cross-Origin headers for actions.json

The actions.json file response must also return valid Cross-Origin headers for GET and OPTIONS requests, specifically the Access-Control-Allow-Origin header value of *.

See actions.json below for more details.

Requisição GET #

O Action client (por exemplo, carteira, extensão de navegador, etc) deve fazer uma requisição HTTP GET JSON para o endpoint da URL da Action.

  • A requisição não deve identificar a carteira ou o usuário.
  • O client deve fazer a requisição com um cabeçalho Accept-Encoding.
  • O client deve exibir o domínio da URL enquanto a requisição está sendo feita.

Resposta GET #

O endpoint da URL da Action (por exemplo, aplicação ou backend de servidor) deve responder com uma resposta HTTP OK JSON (com um payload válido no corpo) ou um erro HTTP apropriado.

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.

Corpo da Resposta GET #

Uma resposta GET com uma resposta HTTP OK JSON deve incluir um payload no corpo que siga a especificação da 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- O valor deve ser uma URL HTTP ou HTTPS absoluta de uma imagem de ícone. O arquivo deve ser uma imagem SVG, PNG ou WebP, ou o client/carteira deve rejeitá-lo como malformado.

  • title- O valor deve ser uma string UTF-8 que representa a origem da requisição de ação. Por exemplo, isso pode ser o nome de uma marca, loja, aplicativo ou pessoa que está fazendo a solicitação.

  • description - O valor deve ser uma string UTF-8 que fornece informações sobre a ação. A descrição deve ser exibida para o usuário.

  • label - O valor deve ser uma string UTF-8 que será renderizada em um botão para o usuário clicar. Todos os rótulos não devem exceder frases de 5 palavras e devem começar com um verbo para solidificar a ação que você deseja que o usuário realize. Por exemplo, "Mint NFT", "Vote Yes" ou "Stake 1 SOL".

  • disabled - O valor deve ser booleano para representar o estado desabilitado do botão renderizado (que exibe o string label). Se nenhum valor for fornecido, disabled deve ser padrão como false (ou seja, habilitado por padrão). Por exemplo, se o endpoint de ação for para uma votação de governança que foi encerrada, defina disabled=true e o label pode ser "Vote Closed".

  • error - Uma indicação opcional de erro não fatal. Se presente, o client deve exibi-la para o usuário. 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 - Um array opcional de ações relacionadas para o endpoint. Os usuários devem ver a UI para cada uma das ações listadas e devem realizar apenas uma. Por exemplo, um endpoint de ação de votação de governança pode retornar três opções para o usuário: "Vote Yes", "Vote No" e "Abstain from Vote".

    • Se nenhum links.actions for fornecido, o client deve renderizar um único botão usando a string label raiz e fazer a requisição POST para o mesmo endpoint de URL de ação que a requisição GET inicial.

    • Se quaisquer links.actions forem fornecidos, o client deve renderizar apenas botões e campos de entrada baseados nos itens listados no campo links.actions. O client não deve renderizar um botão para o conteúdo do label raiz.

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

Exemplo de Resposta GET #

A resposta de exemplo a seguir fornece uma única ação "root" que deve ser apresentada ao usuário como um único botão com o rótulo "Claim Access Token":

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

A resposta de exemplo a seguir fornece 3 links de ações relacionadas que permitem ao usuário clicar em um dos 3 botões para votar em uma proposta de DAO:

{
  "title": "Realms DAO Platform",
  "icon": "<url-to-image>",
  "description": "Vote nas propostas de governança DAO #1234.",
  "label": "Vote",
  "links": {
    "actions": [
      {
        "label": "Vote Yes", // texto do botão
        "href": "/api/proposal/1234/vote?choice=yes"
      },
      {
        "label": "Vote No", // texto do botão
        "href": "/api/proposal/1234/vote?choice=no"
      },
      {
        "label": "Abstain from Vote", // texto do botão
        "href": "/api/proposal/1234/vote?choice=abstain"
      }
    ]
  }
}

Exemplo de Resposta GET com Parâmetros #

A resposta de exemplo a seguir demonstra como aceitar entrada de texto do usuário (via parâmetros) e incluir essa entrada no endpoint da requisição POST final (via o campo href dentro de um LinkedAction):

A resposta de exemplo a seguir fornece ao usuário 3 ações vinculadas para fazer stake de SOL: um botão rotulado "Stake 1 SOL", outro botão rotulado "Stake 5 SOL" e um campo de entrada de texto que permite ao usuário inserir um valor específico "amount" que será enviado para a API Action:

{
  "title": "Stake-o-matic",
  "icon": "<url-to-image>",
  "description": "Stake SOL para ajudar manter a rede da Solana segura",
  "label": "Stake SOL", // não exibido, pois `links.actions` são fornecidos
  "links": {
    "actions": [
      {
        "label": "Stake 1 SOL", // texto do botão
        "href": "/api/stake?amount=1"
        // sem `parameters`, portanto, não é um campo de entrada de texto
      },
      {
        "label": "Stake 5 SOL", // texto do botão
        "href": "/api/stake?amount=5"
        // sem `parameters`, portanto, não é um campo de entrada de texto
      },
      {
        "label": "Stake", // texto do botão
        "href": "/api/stake?amount={amount}",
        "parameters": [
          {
            "name": "amount", // nome do campo
            "label": "Quantidade de SOL" // texto de placeholder
          }
        ]
      }
    ]
  }
}

A resposta de exemplo a seguir fornece um único campo de entrada para o usuário inserir um amount que é enviado com a requisição POST (podendo ser usado como um parâmetro de consulta ou um subcaminho):

{
  "icon": "<url-to-image>",
  "label": "Donate SOL",
  "title": "Doe para a GoodCause Charity",
  "description": "Ajude a apoiar esta instituição de caridade doando SOL.",
  "links": {
    "actions": [
      {
        "label": "Donate", // texto do botão
        "href": "/api/donate/{amount}", // ou /api/donate?amount={amount}
        "parameters": [
          // campo de entrada {amount}
          {
            "name": "amount", // nome do campo de entrada
            "label": "Quantidade de SOL" // texto do placeholder
          }
        ]
      }
    ]
  }
}

Requisição POST #

O client deve fazer uma requisição HTTP POST JSON para a URL da ação com um payload no corpo de:

{
  "account": "<account>"
}
  • account - O valor deve ser a chave pública codificada em base58 de uma conta que pode assinar a transação.

O client deve fazer a requisição com um cabeçalho Accept-Encoding header e a aplicação pode responder com um cabeçalho Content-Encoding header para compressão HTTP.

O client deve exibir o domínio da URL da ação enquanto a requisição está sendo feita. Se uma requisição GET foi feita, o client também deve exibir o title e renderizar a imagem do icon dessa resposta GET.

Resposta POST #

O endpoint POST da Action deve responder com uma resposta HTTP OK JSON (com um payload válido no corpo) ou um erro HTTP apropriado.

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.

Corpo da Resposta POST #

Uma resposta POST com uma resposta HTTP OK JSON deve incluir um payload no corpo 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 - O valor deve ser uma transação transação serializada codificada em base64. O client deve decodificar a transação de base64 e desserializá-la.

  • message - O valor deve ser uma string UTF-8 que descreve a natureza da transação incluída na resposta. O client deve exibir esse valor para o usuário. Por exemplo, pode ser o nome de um item sendo comprado, um desconto aplicado a uma compra ou uma nota de agradecimento.

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

  • O client e a aplicação devem permitir campos adicionais no corpo da requisição e no corpo da resposta, que podem ser adicionados por atualizações futuras da especificação.

Info

A aplicação pode responder com uma transação parcialmente ou totalmente assinada. O client e a carteira devem validar a transação como não confiável.

Resposta POST - Transação #

Se as assinaturas na transação estiverem vazias ou a transação NÃO tiver sido parcialmente assinada:

  • O client deve ignorar o feePayer na transação e definir o feePayer como a account na requisição.
  • O client deve ignorar o recentBlockhash na transação e definir o recentBlockhash como o último blockhash.
  • O client deve serializar e desserializar a transação antes de assiná-la. Isso garante a ordenação consistente das chaves de conta, como uma solução para este problema.

Se a transação foi parcialmente assinada:

  • O client NÃO deve alterar o feePayer ou o recentBlockhash, pois isso invalidaria quaisquer assinaturas existentes.
  • O client deve verificar as assinaturas existentes e, se alguma for inválida, o client deve rejeitar a transação como malformada.

O client deve assinar a transação apenas com a account na requisição, e deve fazê-lo apenas se uma assinatura para a account na requisição for esperada.

Se qualquer assinatura, exceto uma assinatura para a account na requisição, for esperada, o client deve rejeitar a transação 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 no 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 #

O propósito do arquivo actions.json permite que uma aplicação instrua clients sobre quais URLs de site suportam Solana Actions e forneça um mapeamento que pode ser usado para realizar requisições GET para um servidor de API de Actions.

Cross-Origin headers are required

The actions.json file response must also return valid Cross-Origin headers for GET and OPTIONS requests, specifically the Access-Control-Allow-Origin header value of *.

See OPTIONS response above for more details.

O arquivo actions.json deve ser armazenado e acessível universalmente na raiz do domínio.

Por exemplo, se sua aplicação web estiver implantada em my-site.com, então o arquivo actions.json deve ser acessível em https://my-site.com/actions.json. This file should also be Cross-Origin accessible via any browser by having a Access-Control-Allow-Origin header value of *.

Regras #

O campo rules permite que a aplicação mapeie um conjunto de caminhos relativos de rota de um site para um conjunto de outros caminhos.

Tipo: Array de ActionRuleObject.

ActionRuleObject
interface ActionRuleObject {
  /** caminho relativo (preferido) ou absoluto para realizar o mapeamento de regra */
  pathPattern: string;
  /** caminho relativo (preferido) ou absoluto que suporta requisições action */
  apiPath: string;
}
  • pathPattern - Um padrão que corresponde a cada caminho de entrada.

  • apiPath - Um destino definido como um caminho absoluto ou URL externa.

Regras - pathPattern #

Um padrão que corresponde a cada caminho de entrada. Pode ser um caminho absoluto ou relativo e suporta os seguintes formatos:

  • Correspondência Exata: Corresponde exatamente ao caminho da URL.

    • Exemplo: /exact-path
    • Exemplo: https://website.com/exact-path
  • Correspondência Coringa: Usa curingas para corresponder a qualquer sequência de caracteres no caminho da URL. Isso pode corresponder a segmentos únicos (usando *) ou múltiplos segmentos (usando **). (veja Correspondência de Caminhos abaixo).

    • Exemplo: /trade/* corresponderá a /trade/123 e /trade/abc, capturando apenas o primeiro segmento após /trade/.
    • Exemplo: /category/*/item/** corresponderá a /category/123/item/456 e /category/abc/item/def.
    • Exemplo: /api/actions/trade/*/confirm corresponderá a /api/actions/trade/123/confirm.

Regras - apiPath #

O caminho de destino para a requisição action. Pode ser definido como um caminho absoluto ou uma URL externa.

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

Regras - Parâmetros de Consulta #

Os parâmetros de consulta da URL original são sempre preservados e anexados à URL mapeada.

Regras - Correspondência de Caminho #

A tabela a seguir descreve a sintaxe para padrões de correspondência de caminho:

OperadorCorresponde
*Um único segmento de caminho, não incluindo o caractere separador de caminho circundante /.
**```

Corresponde a zero ou mais caracteres, incluindo qualquer caractere separador de caminho / entre múltiplos segmentos de caminho. Se outros operadores estiverem incluídos, o operador ** deve ser o último operador.

| `?`      | Padrão não suportado.                                                                                                                                                                                          |
 
### Exemplos de Regras
 
O exemplo a seguir demonstra uma regra de correspondência exata para mapear requisições para `/buy` da raiz do seu site para o caminho exato `/api/buy` relativo à raiz do seu site:
 
```json filename="actions.json"
{
  "rules": [
    {
      "pathPattern": "/buy",
      "apiPath": "/api/buy"
    }
  ]
}

O exemplo a seguir usa correspondência de caminho coringa para mapear requisições para qualquer caminho (excluindo subdiretórios) sob /actions/ da raiz do seu site para um caminho correspondente sob /api/actions/relativo à raiz do seu site:

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

O exemplo a seguir usa correspondência de caminho coringa para mapear requisições para qualquer caminho (excluindo subdiretórios) sob /donate/ da raiz do seu site para um caminho absoluto correspondente https://api.dialect.com/api/v1/donate/ em um site externo:

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

O exemplo a seguir usa correspondência de caminho com coringa para uma regra idempotente para mapear requisições para qualquer caminho (incluindo subdiretórios) sob /api/actions/ da raiz do seu site para ele mesmo:

Info

Regras idempotentes permitem que clients de blink determinem mais facilmente se um determinado caminho suporta requisições de API action sem precisar ser prefixado com o URI solana-action:ou realizar testes de resposta adicionais.

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

Identidade do Action #

Os endpoints de Action podem incluir uma Identidade de Ação nas transações que são retornadas na resposta POST para o usuário assinar. Isso permite que indexadores e plataformas de análise atribuam facilmente e de maneira verificável a atividade on-chain a um Provedor de Ação específico (ou seja, serviço) de forma verificável.

A Identidade da Action é um par de chaves usado para assinar uma mensagem formatada especialmente que é incluída na transação usando uma instrução de Memo. Essa Identifier Message pode ser atribuída de forma verificável a uma Identidade da Ação específica, e, portanto, atribuir transações a um Provedor de Ação específico.

O par de chaves não é necessário para assinar a própria transação. Isso permite que carteiras e aplicativos melhorem a entregabilidade das transações quando nenhuma outra assinatura está na transação retornada para um usuário (veja transação de resposta POST).

Se o caso de uso de um Provedor de Ação exigir que seus serviços de backend pré-assinem a transação antes do usuário, eles devem usar este par de chaves como sua Identidade de Ação. Isso permitirá que uma conta a menos seja incluída na transação, reduzindo o tamanho total das transações em 32 bytes.

Mensagem de Identificação de Ação #

A Mensagem de Identificação de Ação é uma string UTF-8 separada por dois pontos incluída em uma transação usando uma única instrução SPL Memo.

protocol:identity:reference:signature
  • protocol - O valor do protocolo sendo usado (definido como solana-action conforme o Esquema de URL acima)
  • identity- O valor deve ser o endereço da chave pública codificado em base58 do par de chaves da Identidade de Ação
  • reference - O valor deve ser uma array de 32 bytes codificado em base58. Isso pode ou não ser chaves públicas, on ou off the curve, e pode ou não corresponder a contas na Solana.
  • signature - assinatura codificada em base58 criada a partir do par de chaves da Identidade de Ação assinando apenas o valor reference.

O valor reference deve ser usado apenas uma vez e em uma única transação. Para o propósito de associar transações a um Provedor de Ação, apenas o primeiro uso do valor reference é considerado válido.

As transações podem ter múltiplas instruções Memo. Ao realizar um getSignaturesForAddress, o campo memo dos resultados retornará a mensagem de cada instrução memo como uma única string com cada uma separada por ponto e vírgula.

Nenhum outro dado deve ser incluído na instrução Memo da Mensagem de Identificação.

A identity e a reference devem ser incluídas como chaves de leitura apenas, keys não assinantes na transação em uma instrução que NÃO seja a instrução Memo da Mensagem de Identificação.

A instrução Memo da Mensagem de Identificação deve ter zero contas fornecidas. Se quaisquer contas forem fornecidas, o programa Memo exige que essas contas sejam assinantes válidos. Para fins de identificação de ações, isso restringe a flexibilidade e pode degradar a experiência do usuário. Portanto, é considerado um anti-padrão e deve ser evitado.

Verificação de Identidade do Action #

Qualquer transação que inclua a conta identity pode ser associada verificadamente ao Provedor do Action em um processo de múltiplas etapas:

  1. Obtenha todas as transações para uma determinada identity.
  2. Analise e verifique a string de memo de cada transação, garantindo que a signature é válida para o reference armazenado.
  3. Verifique se a transação específica é a primeira ocorrência on-chain do reference:
    • Se esta transação for a primeira ocorrência, a transação é considerada verificada e pode ser atribuída com segurança ao Provedor do Action.
    • Se esta transação NÃO for a primeira ocorrência, ela é considerada inválida e, portanto, não atribuída ao Provedor do Action.

Como os validadores da Solana indexam as transações pelas chaves das contas, o método RPC getSignaturesForAddress pode ser usado para localizar todas as transações que incluem a conta identity.

A resposta deste método RPC inclui todos os dados do Memo no campo memo. Se várias instruções de Memo foram usadas na transação, cada mensagem de memo será incluída neste campo memo e deve ser analisada adequadamente pelo verificador para obter a Mensagem de Verificação de Identidade.

Essas transações devem ser inicialmente consideradas NÃO VERIFICADAS. Isso ocorre porque a identity não é obrigatória para assinar a transação, o que permite que qualquer transação inclua essa conta como não-signatária. Potencialmente inflando artificialmente os contadores de atribuição e uso.

A Mensagem de Verificação de Identidade deve ser verificada para garantir que a signature foi criada pela identity assinando o reference. Se essa verificação de assinatura falhar, a transação é inválida e não deve ser atribuída ao Provedor do Action.

Se a verificação da assinatura for bem-sucedida, o verificador deve garantir que esta transação seja a primeira ocorrência on-chain do reference. Se não for, a transação é considerada inválida.