LinkedAction
export interface LinkedAction {
/** Type of action to be performed by user */
type: LinkedActionType;
/** 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:

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

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.

Example GET Response

The following example response provides a single "root" action that is expected to be presented to the user a single button with a label of "Claim Access Token":

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

The following example response provides 3 related action links that allow the user to click one of 3 buttons to cast their vote for a DAO proposal:

{
"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"
}
]
}
}

Example GET Response with Parameters

The following examples response demonstrate how to accept text input from the user (via parameters) and include that input in the final POST request endpoint (via the href field within a LinkedAction):

The following example response provides the user with 3 linked actions to stake SOL: a button labeled "Stake 1 SOL", another button labeled "Stake 5 SOL", and a text input field that allows the user to enter a specific "amount" value that will be sent to the Action API:

{
"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
}
]
}
]
}
}

The following example response provides a single input field for the user to enter an amount which is sent with the POST request (either as a query parameter or a subpath can be used):

{
"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
}
]
}
]
}
}

POST Request

The client must make an HTTP POST JSON request to the action URL with a body payload of:

{
"account": "<account>"
}

The client should make the request with an Accept-Encoding header and the application may respond with a Content-Encoding header for HTTP compression.

The client should display the domain of the action URL as the request is being made. If a GET request was made, the client should also display the title and render the icon image from that GET response.

POST Response

The Action's POST endpoint should respond with an HTTP OK JSON response (with a valid payload in the body) or an appropriate HTTP error.

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.

POST Response Body

A POST response with an HTTP OK JSON response should include a body payload of:

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;
};
}

The application may respond with a partially or fully signed transaction. The client and wallet must validate the transaction as untrusted.

POST Response - Transaction

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

If the transaction has been partially signed:

The client must only sign the transaction with the account in the request, and must do so only if a signature for the account in the request is expected.

If any signature except a signature for the account in the request is expected, the client must reject the transaction as malicious.

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:

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

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:

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:

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

The purpose of the actions.json file allows an application to instruct clients on what website URLs support Solana Actions and provide a mapping that can be used to perform GET requests to an Actions API server.

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.

The actions.json file should be stored and universally accessible at the root of the domain.

For example, if your web application is deployed to my-site.com then the actions.json file should be accessible at 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 *.

Rules

The rules field allows the application to map a set of a website's relative route paths to a set of other paths.

Type: Array of 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;
}

Rules - pathPattern

A pattern that matches each incoming pathname. It can be an absolute or relative path and supports the following formats:

Rules - apiPath

The destination path for the action request. It can be defined as an absolute pathname or an external URL.

Rules - Query Parameters

Query parameters from the original URL are always preserved and appended to the mapped URL.

Rules - Path Matching

The following table outlines the syntax for path matching patterns:

OperatorMatches
*A single path segment, not including the surrounding path separator / characters.
**Matches zero or more characters, including any path separator / characters between multiple path segments. If other operators are included, the ** operator must be the last operator.
?Unsupported pattern.

Rules Examples

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"
}
]
}

The following example uses wildcard path matching to map requests to any path (excluding subdirectories) under /actions/ from your site's root to a corresponding path under /api/actions/ relative to your site's root:

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

The following example uses wildcard path matching to map requests to any path (excluding subdirectories) under /donate/ from your site's root to a corresponding absolute path https://api.dialect.com/api/v1/donate/ on an external site:

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

The following example uses wildcard path matching for an idempotent rule to map requests to any path (including subdirectories) under /api/actions/ from your site's root to itself:

Idempotent rules allow blink clients to more easily determine if a given path supports Action API requests without having to be prefixed with the solana-action: URI or performing additional response testing.

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

Action Identity

Action endpoints may include an Action Identity in the transactions that are returned in their POST response for the user to sign. This allows indexers and analytics platforms to easily and verifiably attribute on-chain activity to a specific Action Provider (i.e. service) in a verifiable way.

The Action Identity is a keypair used to sign a specially formatted message that is included in transaction using a Memo instruction. This Identifier Message can be verifiably attributed to a specific Action Identity, and therefore attribute transactions to a specific Action Provider.

The keypair is not required to sign the transaction itself. This allows wallets and applications to improve transaction deliverability when no other signatures are on the transaction returned to a user (see POST response transaction).

If an Action Provider's use case requires their backend services to pre-sign the transaction before the user does, they should use this keypair as their Action Identity. This will allow one less account be included in the transaction, lowering the total transactions size by 32-bytes.

Action Identifier Message

The Action Identifier Message is a colon separate UTF-8 string included in a transaction using a single SPL Memo instruction.

protocol:identity:reference:signature

The reference value must be used only once and in a single transaction. For the purpose of associating transactions with an Action Provider, only the first usage of the reference value is considered valid.

Transactions may have multiple Memo instructions. When performing a getSignaturesForAddress, the results memo field will return each memo instruction's message as a single string with each separated by a semi-colon.

No other data should be included with Identifier Message's Memo instruction.

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.

The Identifier Message Memo instruction must have zero accounts provided. If any accounts are provided, the Memo program requires theses accounts to be valid signers. For the purposes of identifying actions, this restricts flexibility and can degrade the user experience. Therefore it is considered an anti-pattern and must be avoided.

Action Identity Verification

Any transaction that includes the identity account can be verifiably associated with the Action Provider in a multi-step process:

  1. Get all the transactions for a given identity.
  2. Parse and verify each transaction's memo string, ensuring the signature is valid for the reference stored.
  3. Verify the specific transaction is the first on-chain occurrence of the reference on-chain:
    • If this transaction is the first occurrence, the transaction is considered verified and can be safely attributed to the Action Provider.
    • If this transaction is NOT the first occurrence, it is considered invalid and therefore not attributed to the Action Provider.

Because Solana validators index transactions by the account keys, the getSignaturesForAddress RPC method can be used locate all transactions including the identity account.

This RPC method's response includes all the Memo data in the memo field. If multiple Memo instructions were used in the transaction, each memo message will be included in this memo field and must be parsed accordingly by the verifier to obtain the Identity Verification Message.

These transactions should be initially considered UNVERIFIED. This is due to the identity not being required to sign the transaction which allows any transaction to include this account as a non-signer. Potentially artificially inflating attribution and usage counts.

The Identity Verification Message should be checked to ensure the signature was created by the identity signing the reference. If this signature verification fails, the transaction is invalid and should be attributed to the Action Provider.

If the signature verification is successful, the verifier should ensure this transaction is the first on-chain occurrence of the reference. If it is not, the transaction is considered invalid.

上一页

Add Solana to Your Exchange

下一页

Confirmation & Expiration