diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/_category_.yml b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/_category_.yml new file mode 100644 index 0000000000..cb16fc145f --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/_category_.yml @@ -0,0 +1 @@ +label: Websockets Stream API \ No newline at end of file diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/list-subscriptions-message.md b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/list-subscriptions-message.md new file mode 100644 index 0000000000..dfa84cea22 --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/list-subscriptions-message.md @@ -0,0 +1,49 @@ +--- +title: List subscriptions request message format +sidebar_label: Listing subscriptions +sidebar_position: 4 +--- + +# List subscriptions message format + +List subscriptions requests must be sent as JSON in text frames, one request per frame. +This message is different from others as it doesn't require you to provide subscription ID. +Thus, the response for this message is different too. + +### Example of request + +```json +{ + "action": "list_subscriptions" +} +``` + +### Example of response + +```json +{ + "subscriptions": [ + { + "subscription_id": "uuid-1", + "topic": "blocks", + "arguments": { + "block_status": "finalized", + "start_block_height": "123456789" + } + }, + { + "subscription_id": "uuid-2", + "topic": "events", + "arguments": {} + } + ] +} +``` + +If there are no active subscriptions, `subscriptions` array will be empty. + +### Request fields + +| Name | Type | Required | Description | +|----------|--------|----------|-----------------------------------------------------------------------------------------| +| `action` | STRING | YES | Action to perform. Must be `list_subscriptions` to initiate a list subscription request | diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/overview.md b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/overview.md new file mode 100644 index 0000000000..b444d57623 --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/overview.md @@ -0,0 +1,196 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +--- + +# Websockets Stream API + +## Overview + +The Stream API allows clients to receive real-time updates from the Flow blockchain via WebSocket connections. It +supports subscribing to various topics, such as blocks, events, and transactions, enabling low-latency access to live +data. + +### Important Information + +- **Endpoint**: The WebSocket server is available at: + - Mainnet: `wss://rest-mainnet.onflow.org/v1/ws` + - Testnet: `wss://rest-testnet.onflow.org/v1/ws` +- **Limits**: + - Each connection supports up to 20 concurrent subscriptions. Exceeding this limit will result in an error. + - Each subscription may provide up to 20 responses per second. + - After 1 minute of inactivity (no data sent or received) the connection is closed. + +- **Supported Topics**: + - `block_digests` + - `block_headers` + - `blocks` + - `events` + - `account_statuses` + - `transaction_statuses` + - `send_and_get_transaction_statuses` + +- **Notes**: Always handle errors gracefully and close unused subscriptions to maintain efficient connections. + +--- + +## Setting Up a WebSocket Connection + +Use any WebSocket client library to connect to the endpoint. Below is an example using JavaScript: + +```javascript +const ws = new WebSocket('wss://rest-mainnet.onflow.org/ws'); + +ws.onopen = () => { + console.log('Connected to WebSocket server'); +}; + +ws.onclose = () => { + console.log('Disconnected from WebSocket server'); +}; + +ws.onerror = (error) => { + console.error('WebSocket error:', error); +}; +``` + +--- + +## Subscribing to Topics + +To receive data from a specific topic, send a subscription request in JSON format over the WebSocket connection. + +### Request Format + +```json +{ + "subscription_id": "some-id-42", + "action": "subscribe", + "topic": "blocks", + "arguments": { + "block_status": "sealed", + "start_block_height": "123456789" + } +} +``` + +- **`subscription_id`**(optional): A unique identifier for the subscription (a string with maximum length constraint of 20 characters). If omitted, the server generates one. +- **`action`**: The action to perform. Supported actions include: `subscribe`, `unsubscribe`, `list_subscriptions`. +- **`topic`**: The topic to subscribe to. See the supported topics in the Overview. +- **`arguments`**: Additional topic specific arguments for subscriptions, such as `start_block_height`, `start_block_id`, and others. + +### Successful Response Format + +```json +{ + "subscription_id": "some-id-42", + "action": "subscribe" +} +``` + +--- + +## Unsubscribing from Topics + +To stop receiving data from a specific topic, send an unsubscribe request. + +### Request Format + +```json +{ + "subscription_id": "some-id-42", + "action": "unsubscribe" +} +``` + +### Successful Response Format + +```json +{ + "subscription_id": "some-id-42", + "action": "unsubscribe" +} +``` + +--- + +## Listing Active Subscriptions + +You can retrieve a list of all active subscriptions for the current WebSocket connection. + +### Request Format + +```json +{ + "action": "list_subscriptions" +} +``` + +### Successful Response Format + +```json +{ + "subscriptions": [ + { + "subscription_id": "some-id-1", + "topic": "blocks", + "arguments": { + "block_status": "sealed", + "start_block_height": "123456789" + } + }, + { + "subscription_id": "some-id-2", + "topic": "events", + "arguments": {} + } + ] +} +``` + +--- + +## Errors Example + +If a request is invalid or cannot be processed, the server responds with an error message. + +### OK Response + +```json +{ + "subscription_id": "some-id-42", + "topic": "block_digests", + "payload": { + "id": "0x1234...", + "height:": "123456789", + "timestamp": "2025-01-02T10:00:00Z" + } +} +``` + +### Error Response + +```json +{ + "subscription_id": "some-id-42", + "error": { + "code": 500, + "message": "Access Node failed" + } +} +``` + +### Common Error Codes + +- **400**: Invalid message format or arguments +- **404**: Subscription not found +- **500**: Internal server error + +### Asynchronous environments + +If you're working in an asynchronous environment, the Streaming API ensures **first-in first-out** message processing, +so responses will be returned in the same order the requests were received over the connection. +You can leverage this feature to simplify your code and maintain consistency. + +Additionally, you can specify a custom `subscription_id` in the subscribe request to easily identify the correct response. It must not be an empty string and must follow a maximum length constraint of 20 characters. \ No newline at end of file diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-message.md b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-message.md new file mode 100644 index 0000000000..30f400a90c --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-message.md @@ -0,0 +1,83 @@ +--- +title: Subscribe request message format +sidebar_label: Subscribing to topic +sidebar_position: 2 +--- + +# Subscribe request format + +Subscribe requests must be sent as JSON in text frames, one request per frame. + + +### Example of subscribe request + +```json +{ + "subscription_id": "some-id-1", + "action": "subscribe", + "topic": "block_digests", + "arguments": { + "block_status": "finalized", + "start_block_height": "99,416,580" + } +} +``` + +### Example of successful response + +```json +{ + "subscription_id": "some-id-1", + "action": "subscribe" +} +``` + +### Example of failed response + +```json +{ + "subscription_id": "some-id-1", + "error": { + "code": 400, + "message": "invalid message" + } +} +``` + +### Example of messages provided by subscription (if successful) + +```json +{ + "subscription_id": "some-id-1", + "topic": "block_digests", + "payload": { + "id": "0x1234...", + "height:": "123456789", + "timestamp": "2025-01-02T10:00:00Z" + } +} +``` + +### Example of messages provided by subscription (if error) + +```json +{ + "subscription_id": "some-id-1", + "error": { + "code": 500, + "message": "internal error" + } +} +``` + +### Request fields: + +| Name | Type | Required | Description | +|-------------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------------------| +| `subscription_id` | STRING | NO | Optional unique identifier for the subscription. Max length of ID generated by client is 20 characters. Server will generate a unique ID if omitted | +| `action` | STRING | YES | Action to perform. Must be `subscribe` to initiate a subscription | +| `topic` | STRING | YES | The topic to subscribe to, such as `blocks`, `block_digests`, etc. | +| `arguments` | STRING | NO | Additional topic specific parameters for the subscription, such as `start_block_id`, `start_block_height` or other. | + +You can use `subscription_id` as a client-generated identifier to track responses asynchronously. +If you don't provide `subscription_id`, the server will generate one and include it in the response. diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-topics.md b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-topics.md new file mode 100644 index 0000000000..29d01ffae4 --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/subscribe-topics.md @@ -0,0 +1,391 @@ +--- +title: Supported topics +sidebar_label: Supported topics +sidebar_position: 2 +--- + +# Supported Topics + +Below is a list of topics that can be subscribed to in order to receive updates about different states of the Flow blockchain. It is possible to subscribe to each topic multiple times with different configurations based on input arguments. The responses for all topics are aligned with [Flow REST API](/http-api) responses. + +## `block_digests` topic + +Provides a summarized version of block information, including only the block ID, height, and timestamp, each time a new block appears on the blockchain. + +### Example Request + +```json +{ + "subscription_id": "some-id-1", + "action": "subscribe", + "topic": "block_digests", + "arguments": { + "block_status": "sealed", + "start_block_height": "10530102" + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `block_status` | STRING | YES | The status of blocks to subscribe to. Supported values are: `sealed`, `finalized`. | +| `start_block_id` | STRING | NO | The ID of the block from which the subscription starts. If this argument is set, `start_block_height` MUST be empty. | | +| `start_block_height` | STRING | NO | The height of the block from which the subscription starts. If this argument is set, `start_block_id` MUST be empty. | + +If neither `start_block_id` nor `start_block_height` is set, the subscription will start from the latest block based on its status. + +### Example Response + +```json +{ + "subscription_id": "some-id-1", + "topic": "block_digests", + "payload": { + "id": "910b...", + "height": "10530103", + "timestamp": "2024-03-19T15:22:12.600529133Z", + } +} +``` + +## `block_headers` topic + +Provides block headers without the payload, each time a new block appears on the blockchain. + +### Example Request + +```json +{ + "subscription_id": "some-id-2", + "action": "subscribe", + "topic": "block_headers", + "arguments": { + "block_status": "sealed", + "start_block_height": "10,530,102" + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `block_status` | STRING | YES | The status of blocks to subscribe to. Supported values are: `sealed`, `finalized`. | +| `start_block_id` | STRING | NO | The ID of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_height`. | | +| `start_block_height` | STRING | NO | The height of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_id` | + +If neither `start_block_id` nor `start_block_height` is set, the subscription will start from the latest block based on its status. + +### Example Response + +```json +{ + "subscription_id": "some-id-2", + "topic": "block_headers", + "payload": { + "id": "910b...", + "parent_id": "1f5b...", + "height": "10530103", + "timestamp": "2024-03-19T15:22:12.600529133Z", + "parent_voter_signature": "+GyIA..." + } +} +``` + +## `blocks` topic + +Provides full block information each time a new block appears on the blockchain. + +### Example Request + +```json +{ + "subscription_id": "some-id-3", + "action": "subscribe", + "topic": "blocks", + "arguments": { + "block_status": "sealed", + "start_block_height": "10,530,102" + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `block_status` | STRING | YES | The status of blocks to subscribe to. Supported values are: `sealed`, `finalized`. | +| `start_block_id` | STRING | NO | The ID of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_height`. | | +| `start_block_height` | STRING | NO | The height of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_id` | + +If neither `start_block_id` nor `start_block_height` is set, the subscription will start from the latest block based on its status. + +### Example Response + +```json +{ + "subscription_id": "some-id-3", + "topic": "blocks", + "payload": { + "header": { + //... + }, + "payload": { + //... + }, + "_expandable": { + //... + }, + "_links": { + //... + }, + "block_status": "BLOCK_SEALED" + } +} +``` + +## `events` topic + +Provides blockchain events. The response can be configured using additional arguments to filter and retrieve only filtered events instead of all events. + +### Example Request + +```json +{ + "subscription_id": "some-id-4", + "action": "subscribe", + "topic": "events", + "arguments": { + "start_block_height": "10530103", + "event_types": ["flow.AccountKeyAdded"] + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `start_block_id` | STRING | NO | The ID of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_height`. | | +| `start_block_height` | STRING | NO | The height of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_id` | +| `heartbeat_interval` | STRING | NO | Maximum number of blocks between messages after which a response with no events is returned. This helps the client track progress for sparse event filters. | +| `event_types` | LIST | NO | A comma-separated list of event types to include. | +| `addresses` | LIST | NO | A comma-separated list of addresses who's events should be included. | +| `contracts` | LIST | NO | A comma-separated list of contracts who's events should be included. | + +If neither `start_block_id` nor `start_block_height` is set, the subscription will start from the latest block based on its status. + +### Example Response + +```json +{ + "subscription_id": "some-id-4", + "topic": "events", + "payload": { + "block_id": "0385...", + "block_height": "10530103", + "block_timestamp": "2024-03-19T15:22:12.600529133Z", + "events": [ + { + "type": "flow.AccountKeyAdded", + "transaction_id": "3dfe...", + "transaction_index": "0", + "event_index": "0", + "payload": "2IGC..." + }, + //... + ], + "message_index": 1 + } +} +``` + +## `account_statuses` topic + +Provides accounts statuses updates. The response can be configured using additional arguments to filter and retrieve only filtered account statuses instead of all core account events. + +### Example Request + +```json +{ + "subscription_id": "some-id-5", + "action": "subscribe", + "topic": "account_statuses", + "arguments": { + "start_block_height": "10530103", + "event_types": ["flow.AccountCreated"] + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `start_block_id` | STRING | NO | The ID of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_height`. | | +| `start_block_height` | STRING | NO | The height of the block from which the subscription starts. If this argument is set, it is **not** possible to set `start_block_id` | +| `heartbeat_interval` | STRING | NO | Maximum number of blocks between messages after which a response with no events is returned. This helps the client track progress for sparse event filters. | +| `event_types` | LIST | NO | A comma-separated list of event types to include. | +| `account_addresses` | LIST | NO | A comma-separated list of addresses who's events should be included. | + +If neither `start_block_id` nor `start_block_height` is set, the subscription will start from the latest block based on its status. + +### Example Response + +```json +{ + "subscription_id": "some-id-5", + "topic": "events", + "payload": { + "block_id": "3a414...", + "height": "10530103", + "account_events": { + "0xe521...": [ + { + "type": "flow.AccountCreated", + "transaction_id": "eb6e...", + "transaction_index": "1", + "event_index": "13", + "payload": "2IGCgd..." + }, + //... + ] + }, + "message_index": 1 + } +} +``` + +## `transaction_statuses` topic + +Provides updates on transaction status changes for already sent transactions. + +### Example Request + +```json +{ + "subscription_id": "some-id-6", + "action": "subscribe", + "topic": "transaction_statuses", + "arguments": { + "tx_id": "7b028..." + } +} +``` + +#### Request Arguments + +| Name | Type | Required | Description | +| ------- | ------ | -------- | -------------------------------------------------------- | +| `tx_id` | STRING | YES | The ID of the transaction to monitor for status changes. | + + +### Example Response + +```json +{ + "subscription_id": "some-id-6", + "topic": "transaction_statuses", + "payload": { + "transaction_result": { + "block_id": "", + "collection_id": "", + "execution": "Pending", + "status": "Pending", + "status_code": 0, + "error_message": "", + "computation_used": "0", + "events": [], + "_links": { + "_self": "/v1/transaction_results/7b02..." + } + }, + "message_index": 0 + } +} +``` + + +## `send_and_get_transaction_statuses` topic + +Sends a transaction and provides updates on its status changes. + +### Example Request + +```json +{ + "subscription_id": "some-id-7", + "action": "subscribe", + "topic": "send_and_get_transaction_statuses", + "arguments": { + "script": "access(all) fun main() {}", + "arguments": [], + "reference_block_id": "a3f1c4d5...", + "gas_limit": "1000", + "payer": "01cf...", + "proposal_key": { + "address": "01cf...", + "key_index": 0, + "sequence_number": 42 + }, + "authorizers": [ + "01cf...", + "179b..." + ], + "payload_signatures": [ + //... + ], + "envelope_signatures": [ + //... + ] + } +} +``` + +| Name | Type | REQUIRED | Description | +| --------------------- | ------ | -------- | ------------------------------------------------------------------------- | +| `script` | STRING | YES | Base64-encoded content of the Cadence script. | +| `arguments` | LIST | YES | A list of arguments, each encoded as Base64. | +| `reference_block_id` | STRING | YES | BlockID for the transaction's reference block | +| `gas_limit` | STRING | YES | The limit on the amount of computation a transaction can perform. | +| `payer` | STRING | YES | The 8-byte address of an account. | +| `proposal_key` | OBJECT | YES | A required object representing the proposal key. | +| `authorizers` | LIST | YES | A list of authorizers, each represented as a hexadecimal-encoded address. | +| `payload_signatures` | LIST | NO | A list of Base64-encoded signatures. | +| `envelope_signatures` | LIST | YES | A list of Base64-encoded signatures. | + +### Example Response + +```json +{ + "subscription_id": "some-id-7", + "topic": "send_and_get_transaction_statuses", + "payload": { + "transaction_result": { + "block_id": "e613...", + "collection_id": "3c48...", + "execution": "Success", + "status": "Sealed", + "status_code": 0, + "error_message": "", + "computation_used": "0", + "events": [ + { + "type": "A.4eb8a10cb9f87357.NFTStorefront.ListingAvailable", + "transaction_id": "7b02878855772537176dbf3c48c44bc93c4a55be2a5e7b7fb3641e4295343473", + "transaction_index": "1", + "event_index": "0", + "payload": "eyJ2...." + }, + //... + ], + "_links": { + "_self": "/v1/transaction_results/7b02..." + } + }, + "message_index": 3 + } +} +``` \ No newline at end of file diff --git a/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/unsubscribe-message.md b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/unsubscribe-message.md new file mode 100644 index 0000000000..069e0b0e4a --- /dev/null +++ b/docs/networks/node-ops/access-onchain-data/access-nodes/accessing-data/websockets-stream-api/unsubscribe-message.md @@ -0,0 +1,45 @@ +--- +title: Unsubscribe request message format +sidebar_label: Unsubscribing from topic +sidebar_position: 3 +--- + +# Unsubscribe message format + +Unsubscribe requests must be sent as JSON in text frames, one request per frame. + +### Example of unsubscribe request + +```json +{ + "subscription_id": "some-id-1", + "action": "unsubscribe" +} +``` + +### Example of successful response + +```json +{ + "subscription_id": "some-id-1", + "action": "unsubscribe" +} +``` + +### Example of error response + +```json +{ + "error": { + "code": 404, + "message": "subscription not found" + } +} +``` + +### Request fields + +| Name | Type | Required | Description | +|-------------------|--------|----------|-----------------------------------------------------------------------| +| `subscription_id` | STRING | YES | Unique identifier of the subscription | +| `action` | STRING | YES | Action to perform. Must be `unsubscribe` to initiate a unsubscription |