From 5046d2fab3956e8a8456e7d86d89bd04ebc5226e Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:36:40 -0400 Subject: [PATCH 1/6] feat: input types --- packages/actions-spec/README.md | 128 ++++++++- packages/actions-spec/SPEC-v2.0.md | 435 +++++++++++++++++++++++++++++ packages/actions-spec/index.d.ts | 37 ++- 3 files changed, 597 insertions(+), 3 deletions(-) create mode 100644 packages/actions-spec/SPEC-v2.0.md diff --git a/packages/actions-spec/README.md b/packages/actions-spec/README.md index cd15fe1..1b92217 100644 --- a/packages/actions-spec/README.md +++ b/packages/actions-spec/README.md @@ -225,11 +225,24 @@ export interface LinkedAction { /** button text rendered to the user */ label: string; /** Parameter to accept user input within an action */ - parameters?: [ActionParameter]; + parameters?: Array; } +``` + +The `ActionParameter` allows declaring what input the Action API is requesting +from the user: -/** Parameter to accept user input within an action */ +```ts filename="ActionParameter" +/** + * Parameter to accept user input within an action + */ export interface ActionParameter { + /** input field type */ + type?: ActionParameterType; + /** regular expression pattern to validate user input client side */ + pattern?: string; + /** human readable description of the `pattern` */ + patternDescription?: string; /** parameter name in url */ name: string; /** placeholder text for the user input field */ @@ -239,6 +252,117 @@ export interface ActionParameter { } ``` +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 message that describes the regular +expression pattern. If `pattern` is provided, the `patternDescription` is +required to be provided. + +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](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). + +```ts filename="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. +``) to provide better client side validation and user +experience: + +- `text` - equivalent of HTML + [“text” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text) + element +- `email` - equivalent of HTML + [“email” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email) + element +- `url` - equivalent of HTML + [“url” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/url) + element +- `number` - equivalent of HTML + [“number” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number) + element +- `date` - equivalent of HTML + [“date” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date) + element +- `datetime-local` - equivalent of HTML + [“datetime-local” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local) + element +- `checkbox` - equivalent to a grouping of standard HTML + [“checkbox” input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox) + 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](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio) + 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](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea). + Allowing the user provide multi-line input. +- `select` - equivalent of HTML + [select element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select), + 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). + +```ts filename="ActionParameterSelectable" +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-client 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. + ### POST Request The client must make an HTTP `POST` JSON request to the action URL with a body diff --git a/packages/actions-spec/SPEC-v2.0.md b/packages/actions-spec/SPEC-v2.0.md new file mode 100644 index 0000000..cd15fe1 --- /dev/null +++ b/packages/actions-spec/SPEC-v2.0.md @@ -0,0 +1,435 @@ +# Solana Actions and Blockchain Links (Blinks) + +[Read the docs to get started](https://solana.com/docs/advanced/actions) + +Solana Actions are specification-compliant APIs that return transactions on the +Solana blockchain to be previewed, signed, and sent across a number of various +contexts, including QR codes, buttons + widgets, and websites across the +internet. Actions make it simple for developers to integrate the things you can +do throughout the Solana ecosystem right into your environment, allowing you to +perform blockchain transactions without needing to navigate away to a different +app or webpage. + +Blockchain links – or blinks – turn any Solana Action into a shareable, +metadata-rich link. Blinks allow Action-aware clients (browser extension +wallets, bots) to display additional capabilities for the user. On a website, a +blink might immediately trigger a transaction preview in a wallet without going +to a decentralized app; in Discord, a bot might expand the blink into an +interactive set of buttons. This pushes the ability to interact on-chain to any +web surface capable of displaying a URL. + +## Contributing + +If you would like to propose an update the Solana Actions specification, please +publish a proposal on the official Solana forum under the Solana Request for +Comments (sRFC) section: https://forum.solana.com/c/srfc/6 + +## Specification + +The Solana Actions specification consists of key sections that are part of a +request/response interaction flow: + +- Solana Action [URL scheme](#url-scheme) providing an Action URL +- [OPTIONS response](#options-response) to an Action URL to pass CORS + requirements +- [GET request](#get-request) to an Action URL +- [GET response](#get-response) from the server +- [POST request](#post-request) to an Action URL +- [POST response](#post-response) from the server + +Each of these requests are made by the _Action client_ (e.g. wallet app, browser +extension, dApp, website, etc) to gather specific metadata for rich user +interfaces and to facilitate user input to the Actions API. + +Each of the responses are crafted by an application (e.g. website, server +backend, etc) and returned to the _Action client_. Ultimately, providing a +signable transaction or message for a wallet to prompt the user to approve, +sign, and send to the blockchain. + +### URL Scheme + +A Solana Action URL describes an interactive request for a signable Solana +transaction or message using the `solana-action` protocol. + +The request is interactive because the parameters in the URL are used by a +client to make a series of standardized HTTP requests to compose a signable +transaction or message for the user to sign with their wallet. + +```text +solana-action: +``` + +- A single `link` field is required as the pathname. The value must be a + conditionally + [URL-encoded](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) + absolute HTTPS URL. + +- If the URL contains query parameters, it must be URL-encoded. URL-encoding the + value prevents conflicting with any Actions protocol parameters, which may be + added via the protocol specification. + +- If the URL does not contain query parameters, it should not be URL-encoded. + This produces a shorter URL and a less dense QR code. + +In either case, clients must +[URL-decode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) +the value. This has no effect if the value isn't URL-encoded. If the decoded +value is not an absolute HTTPS URL, the wallet must reject it as **malformed**. + +### OPTIONS response + +In order to allow Cross-Origin Resource Sharing +([CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)) within Actions +clients (including blinks), all Action endpoints should respond to HTTP requests +for the `OPTIONS` method with valid headers that will allow clients to pass CORS +checks for all subsequent requests from their same origin domain. + +An Actions client may perform +"[preflight](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests)" +requests to the Action URL endpoint in order check if the subsequent GET request +to the Action URL will pass all CORS checks. These CORS preflight checks are +made using the `OPTIONS` HTTP method and should respond with all required HTTP +headers that will allow Action clients (like blinks) to properly make all +subsequent requests from their origin domain. + +At a minimum, the required HTTP headers include: + +- `Access-Control-Allow-Origin` with a value of `*` + - this ensures all Action clients can safely pass CORS checks in order to make + all required requests +- `Access-Control-Allow-Methods` with a value of `GET,POST,PUT,OPTIONS` + - ensures all required HTTP request methods are supported for Actions +- `Access-Control-Allow-Headers` with a minimum value of + `Content-Type, Authorization, Content-Encoding, Accept-Encoding` + +For simplicity, developers should consider returning the same response and +headers to `OPTIONS` requests as their [`GET` response](#get-response). + +> 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](#actionsjson) below for more details. + +### GET Request + +The Action client (e.g. wallet, browser extension, etc) should make an HTTP +`GET` JSON request to the Action's URL endpoint. + +- The request should not identify the wallet or the user. +- The client should make the request with an + [`Accept-Encoding` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding). +- The client should display the domain of the URL as the request is being made. + +### GET Response + +The Action's URL endpoint (e.g. application or server backend) should respond +with an HTTP `OK` JSON response (with a valid payload in the body) or an +appropriate HTTP error. + +- The client must handle HTTP + [client errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses), + [server errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses), + and + [redirect responses](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). +- The endpoint should respond with a + [`Content-Encoding` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) + for HTTP compression. +- The endpoint should respond with a + [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) + of `application/json`. + +- The client should not cache the response except as instructed by + [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#controlling_caching) + response headers. +- The client should display the `title` and render the `icon` image to user. + +#### GET Response Body + +A `GET` response with an HTTP `OK` JSON response should include a body payload +that follows the interface specification: + +```ts filename="ActionGetResponse" +export interface ActionGetResponse { + /** 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; +} +``` + +- `icon` - The value must be an absolute HTTP or HTTPS URL of an icon image. The + file must be an SVG, PNG, or WebP image, or the client/wallet must reject it + as **malformed**. + +- `title` - The value must be a UTF-8 string that represents the source of the + action request. For example, this might be the name of a brand, store, + application, or person making the request. + +- `description` - The value must be a UTF-8 string that provides information on + the action. The description should be displayed to the user. + +- `label` - The value must be a UTF-8 string that will be rendered on a button + for the user to click. All labels should not exceed 5 word phrases and should + start with a verb to solidify the action you want the user to take. For + example, "Mint NFT", "Vote Yes", or "Stake 1 SOL". + +- `disabled` - The value must be boolean to represent the disabled state of the + rendered button (which displays the `label` string). If no value is provided, + `disabled` should default to `false` (i.e. enabled by default). For example, + if the action endpoint is for a governance vote that has closed, set + `disabled=true` and the `label` could be "Vote Closed". + +- `error` - An optional error indication for non-fatal errors. If present, the + client should display it to the user. If set, it should not prevent the client + from interpreting the action or displaying it to the user. 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. + +```ts filename="ActionError" +export interface ActionError { + /** non-fatal error message to be displayed to the user */ + message: string; +} +``` + +- `links.actions` - An optional array of related actions for the endpoint. Users + should be displayed UI for each of the listed actions and expected to only + perform one. For example, a governance vote action endpoint may return three + options for the user: "Vote Yes", "Vote No", and "Abstain from Vote". + + - If no `links.actions` is provided, the client should render a single button + using the root `label` string and make the POST request to the same action + URL endpoint as the initial GET request. + + - If any `links.actions` are provided, the client should only render buttons + and input fields based on the items listed in the `links.actions` field. The + client should not render a button for the contents of the root `label`. + +```ts filename="LinkedAction" +export interface LinkedAction { + /** URL endpoint for an action */ + href: string; + /** button text rendered to the user */ + label: string; + /** Parameter to accept user input within an action */ + parameters?: [ActionParameter]; +} + +/** Parameter to accept user input within an action */ +export interface ActionParameter { + /** 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; +} +``` + +### POST Request + +The client must make an HTTP `POST` JSON request to the action URL with a body +payload of: + +```json +{ + "account": "" +} +``` + +- `account` - The value must be the base58-encoded public key of an account that + may sign the transaction. + +The client should make the request with an +[Accept-Encoding header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) +and the application may respond with a +[Content-Encoding header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) +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. + +- The client must handle HTTP + [client errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses), + [server errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses), + and + [redirect responses](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). +- The endpoint should respond with a + [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) + of `application/json`. + +#### POST Response Body + +A `POST` response with an HTTP `OK` JSON response should include a body payload +of: + +```ts filename="ActionPostResponse" +export interface ActionPostResponse { + /** base64 encoded serialized transaction */ + transaction: string; + /** describes the nature of the transaction */ + message?: string; +} +``` + +- `transaction` - The value must be a base64-encoded + [serialized transaction](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#serialize). + The client must base64-decode the transaction and + [deserialize it](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#from). + +- `message` - The value must be a UTF-8 string that describes the nature of the + transaction included in the response. The client should display this value to + the user. For example, this might be the name of an item being purchased, a + discount applied to a purchase, or a thank you note. + +- The client and application should allow additional fields in the request body + and response body, which may be added by future specification updates. + +> 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`](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#signatures) +are empty or the transaction has NOT been partially signed: + +- The client must ignore the + [`feePayer`](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#feePayer) + in the transaction and set the `feePayer` to the `account` in the request. +- The client must ignore the + [`recentBlockhash`](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#recentBlockhash) + in the transaction and set the `recentBlockhash` to the + [latest blockhash](https://solana-labs.github.io/solana-web3.js/classes/Connection.html#getLatestBlockhash). +- The client must serialize and deserialize the transaction before signing it. + This ensures consistent ordering of the account keys, as a workaround for + [this issue](https://github.com/solana-labs/solana/issues/21722). + +If the transaction has been partially signed: + +- The client must NOT alter the + [`feePayer`](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#feePayer) + or + [`recentBlockhash`](https://solana-labs.github.io/solana-web3.js/classes/Transaction.html#recentBlockhash) + as this would invalidate any existing signatures. +- The client must verify existing signatures, and if any are invalid, the client + must reject the transaction as **malformed**. + +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**. + +### actions.json + +The purpose of the [`actions.json` file](#actionsjson) 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](#get-request) to an Actions +API server. + +> 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](#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`. + +```ts filename="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`](#rules-pathpattern) - A pattern that matches each incoming + pathname. + +- [`apiPath`](#rules-apipath) - A location destination defined as an absolute + pathname or external URL. + +#### Rules - pathPattern + +A pattern that matches each incoming pathname. It can be an absolute or relative +path and supports the following formats: + +- **Exact Match**: Matches the exact URL path. + + - Example: `/exact-path` + - Example: `https://website.com/exact-path` + +- **Wildcard Match**: Uses wildcards to match any sequence of characters in the + URL path. This can match single (using `*`) or multiple segments (using `**`). + (see [Path Matching](#rules-path-matching) below). + + - Example: `/trade/*` will match `/trade/123` and `/trade/abc`, capturing only + the first segment after `/trade/`. + - Example: `/category/*/item/**` will match `/category/123/item/456` and + `/category/abc/item/def`. + - Example: `/api/actions/trade/*/confirm` will match + `/api/actions/trade/123/confirm`. + +#### Rules - apiPath + +The destination path for the action request. It can be defined as an absolute +pathname or an external URL. + +- Example: `/api/exact-path` +- Example: `https://api.example.com/v1/donate/*` +- Example: `/api/category/*/item/*` +- Example: `/api/swap/**` + +#### 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: + +| Operator | Matches | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `*` | 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. | + +## License + +The Solana Actions JavaScript SDK is open source and available under the Apache +License, Version 2.0. See the [LICENSE](./LICENSE) file for more info. diff --git a/packages/actions-spec/index.d.ts b/packages/actions-spec/index.d.ts index 96fd51b..61f3303 100644 --- a/packages/actions-spec/index.d.ts +++ b/packages/actions-spec/index.d.ts @@ -73,13 +73,19 @@ export interface LinkedAction { /** button text rendered to the user */ label: string; /** parameters used to accept user input within an action */ - parameters?: ActionParameter[]; + parameters?: Array; } /** * Parameter to accept user input within an action */ export interface ActionParameter { + /** input field type */ + type?: ActionParameterType; + /** regular expression pattern to validate user input client side */ + pattern?: string; + /** human readable description of the `pattern` */ + patternDescription?: string; /** parameter name in url */ name: string; /** placeholder text for the user input field */ @@ -88,6 +94,35 @@ export interface ActionParameter { required?: boolean; } +/** + * Input field type to present to the user. Normally resembling the respective + * [HTML `input` types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) + * or standard HTML element (e.g. `select`) for the platform being used (web, mobile, etc). + * @default `text` + */ +export type ActionParameterType = + | "text" + | "email" + | "url" + | "number" + | "date" + | "datetime-local" + | "checkbox" + | "radio" + | "textarea" + | "select"; + +export 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; + }>; +} + /** * Response body payload sent via the Action POST Request */ From 2bbf907ea6122c18492c0c2ded46536e2ee43fca Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:43:28 -0400 Subject: [PATCH 2/6] chore: changeset --- packages/actions-spec/.changeset/soft-bears-perform.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/actions-spec/.changeset/soft-bears-perform.md diff --git a/packages/actions-spec/.changeset/soft-bears-perform.md b/packages/actions-spec/.changeset/soft-bears-perform.md new file mode 100644 index 0000000..f4aadae --- /dev/null +++ b/packages/actions-spec/.changeset/soft-bears-perform.md @@ -0,0 +1,6 @@ +--- +"@solana/actions-spec": minor +--- + +added support for more input types for blinks - see sRFC #29 - +https://forum.solana.com/t/srfc-29-input-types-of-blinks-and-actions/1804 From 801553b517d1c0b62c82f836db898d223ffd9c4f Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:47:02 -0400 Subject: [PATCH 3/6] fix: pr workflow --- .github/workflows/pull-requests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 38fbc84..1f028d3 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: ./package + working-directory: ./packages/solana-actions strategy: matrix: From 7bee3bfc0d803b00bde0ffbac2ce0e1a6448b60b Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:55:54 -0400 Subject: [PATCH 4/6] feat: improved types --- packages/actions-spec/README.md | 15 +++++++++++ packages/actions-spec/index.d.ts | 43 +++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/packages/actions-spec/README.md b/packages/actions-spec/README.md index 1b92217..bbafa69 100644 --- a/packages/actions-spec/README.md +++ b/packages/actions-spec/README.md @@ -18,6 +18,16 @@ to a decentralized app; in Discord, a bot might expand the blink into an interactive set of buttons. This pushes the ability to interact on-chain to any web surface capable of displaying a URL. +## Simplified Type Definitions + +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](https://github.com/solana-developers/solana-actions/blob/main/packages/actions-spec/index.d.ts). + ## Contributing If you would like to propose an update the Solana Actions specification, please @@ -270,6 +280,8 @@ fields, providing better client side validation and improving the user experience. In many cases, this type will resemble the standard [HTML input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). +The `ActionParameterType` can be simplified to the following type: + ```ts filename="ActionParameterType" /** * Input field type to present to the user @@ -339,6 +351,9 @@ 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: + ```ts filename="ActionParameterSelectable" interface ActionParameterSelectable extends ActionParameter { options: Array<{ diff --git a/packages/actions-spec/index.d.ts b/packages/actions-spec/index.d.ts index 61f3303..1aeea1f 100644 --- a/packages/actions-spec/index.d.ts +++ b/packages/actions-spec/index.d.ts @@ -73,15 +73,21 @@ export interface LinkedAction { /** button text rendered to the user */ label: string; /** parameters used to accept user input within an action */ - parameters?: Array; + parameters?: TypedParameter[]; } +export type TypedParameter< + T extends ActionParameterType = ActionParameterType, +> = T extends SelectableParameterType + ? ActionParameterSelectable + : ActionParameter; + /** * Parameter to accept user input within an action */ -export interface ActionParameter { +export interface ActionParameter { /** input field type */ - type?: ActionParameterType; + type?: T; /** regular expression pattern to validate user input client side */ pattern?: string; /** human readable description of the `pattern` */ @@ -94,6 +100,17 @@ export interface ActionParameter { required?: boolean; } +export type GeneralParameterType = + | "text" + | "email" + | "url" + | "number" + | "date" + | "datetime-local" + | "textarea"; + +export type SelectableParameterType = "select" | "radio" | "checkbox"; + /** * Input field type to present to the user. Normally resembling the respective * [HTML `input` types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) @@ -101,24 +118,20 @@ export interface ActionParameter { * @default `text` */ export type ActionParameterType = - | "text" - | "email" - | "url" - | "number" - | "date" - | "datetime-local" - | "checkbox" - | "radio" - | "textarea" - | "select"; + | GeneralParameterType + | SelectableParameterType; -export interface ActionParameterSelectable extends ActionParameter { +export interface ActionParameterSelectable + extends Omit, "pattern" | "patternDescription"> { + /** + * Listing of the options the user should be able to select from + */ 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 */ + /** whether this option should be selected by default */ selected?: boolean; }>; } From 0b96615d39bc1a68d20cf19ae399b32fe7b904eb Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:03:21 -0400 Subject: [PATCH 5/6] feat: added min/max and better typing --- packages/actions-spec/README.md | 23 ++++++++++++++----- packages/actions-spec/index.d.ts | 38 +++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/actions-spec/README.md b/packages/actions-spec/README.md index bbafa69..f196db5 100644 --- a/packages/actions-spec/README.md +++ b/packages/actions-spec/README.md @@ -249,16 +249,20 @@ from the user: export interface ActionParameter { /** input field type */ type?: ActionParameterType; - /** regular expression pattern to validate user input client side */ - pattern?: string; - /** human readable description of the `pattern` */ - patternDescription?: string; /** 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; } ``` @@ -267,10 +271,17 @@ 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 message that describes the regular -expression pattern. If `pattern` is provided, the `patternDescription` is +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 +`type`s of `date` or `datetime-local`, these values should be a string dates. +For other string based input `type`s, 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. diff --git a/packages/actions-spec/index.d.ts b/packages/actions-spec/index.d.ts index 1aeea1f..eb3b8cd 100644 --- a/packages/actions-spec/index.d.ts +++ b/packages/actions-spec/index.d.ts @@ -73,10 +73,10 @@ export interface LinkedAction { /** button text rendered to the user */ label: string; /** parameters used to accept user input within an action */ - parameters?: TypedParameter[]; + parameters?: TypedActionParameter[]; } -export type TypedParameter< +export type TypedActionParameter< T extends ActionParameterType = ActionParameterType, > = T extends SelectableParameterType ? ActionParameterSelectable @@ -85,22 +85,32 @@ export type TypedParameter< /** * Parameter to accept user input within an action */ -export interface ActionParameter { +export interface ActionParameter> { /** input field type */ type?: T; - /** regular expression pattern to validate user input client side */ - pattern?: string; - /** human readable description of the `pattern` */ - patternDescription?: string; /** 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?: M; + /** the maximum value allowed based on the `type` */ + max?: M; } -export type GeneralParameterType = +type MinMax = T extends "date" | "datetime-local" + ? string + : T extends "radio" | "select" + ? never + : number; + +type GeneralParameterType = | "text" | "email" | "url" @@ -109,7 +119,7 @@ export type GeneralParameterType = | "datetime-local" | "textarea"; -export type SelectableParameterType = "select" | "radio" | "checkbox"; +type SelectableParameterType = "select" | "radio" | "checkbox"; /** * Input field type to present to the user. Normally resembling the respective @@ -122,7 +132,7 @@ export type ActionParameterType = | SelectableParameterType; export interface ActionParameterSelectable - extends Omit, "pattern" | "patternDescription"> { + extends Omit, "pattern"> { /** * Listing of the options the user should be able to select from */ @@ -139,9 +149,15 @@ export interface ActionParameterSelectable /** * Response body payload sent via the Action POST Request */ -export interface ActionPostRequest { +export interface ActionPostRequest { /** base58-encoded public key of an account that may sign the transaction */ account: string; + /** + * Key-value map of parameter values from user's input + * - key - parameter name + * - value - input value (by default type of `string`, if multi-option, type of `Array` + */ + data?: Record>; } /** From 0ec4c2b120bb148a33eb04b03815053a39849390 Mon Sep 17 00:00:00 2001 From: nickfrosty <75431177+nickfrosty@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:42:05 -0400 Subject: [PATCH 6/6] refactor: notes for simplified types --- packages/actions-spec/README.md | 12 ++++++++++-- packages/actions-spec/index.d.ts | 8 ++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/actions-spec/README.md b/packages/actions-spec/README.md index f196db5..be2f50c 100644 --- a/packages/actions-spec/README.md +++ b/packages/actions-spec/README.md @@ -234,8 +234,12 @@ export interface LinkedAction { href: string; /** button text rendered to the user */ label: string; - /** Parameter to accept user input within an action */ - parameters?: Array; + /** + * Parameters to accept user input within an action + * @see {ActionParameter} + * @see {ActionParameterSelectable} + */ + parameters?: Array; } ``` @@ -245,6 +249,7 @@ from the user: ```ts filename="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 */ @@ -366,6 +371,9 @@ This `ActionParameterSelectable` can be simplified to the following type definition: ```ts filename="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 */ diff --git a/packages/actions-spec/index.d.ts b/packages/actions-spec/index.d.ts index eb3b8cd..8ab230f 100644 --- a/packages/actions-spec/index.d.ts +++ b/packages/actions-spec/index.d.ts @@ -72,8 +72,12 @@ export interface LinkedAction { href: string; /** button text rendered to the user */ label: string; - /** parameters used to accept user input within an action */ - parameters?: TypedActionParameter[]; + /** + * Parameters used to accept user input within an action + * @see {ActionParameter} + * @see {ActionParameterSelectable} + */ + parameters?: Array; } export type TypedActionParameter<