diff --git a/.vscode/launch.json b/.vscode/launch.json index ed21c43273..735ac1c3ea 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", "env": { - "NODE_ENV": "development" + "NODE_ENV": "production" } }, { diff --git a/client/src/generated/schema.d.ts b/client/src/generated/schema.d.ts index e6a1808fe5..59af5ea53d 100644 --- a/client/src/generated/schema.d.ts +++ b/client/src/generated/schema.d.ts @@ -1730,6 +1730,46 @@ export interface paths { patch?: never; trace?: never; }; + "/extended/v3/transactions/{tx_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get transaction + * @description Retrieves details for a given transaction, including both mined and mempool transactions + */ + get: operations["get_transaction"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/extended/v3/transactions/{tx_id}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get transaction events + * @description Retrieves events for a given transaction ID + */ + get: operations["get_transaction_events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/extended/v3/mempool/transactions": { parameters: { query?: never; @@ -31823,7 +31863,10 @@ export interface operations { recipient: string; /** @description Transfer amount as Integer string (64-bit unsigned integer) */ amount: string; - memo: string | null; + memo: { + hex: string; + repr: string; + } | null; }; } | { /** @description Transaction ID */ @@ -32155,7 +32198,10 @@ export interface operations { recipient: string; /** @description Transfer amount as Integer string (64-bit unsigned integer) */ amount: string; - memo: string | null; + memo: { + hex: string; + repr: string; + } | null; }; } | { /** @description Transaction ID */ @@ -32390,16 +32436,20 @@ export interface operations { }; }; }; - get_mempool_transactions: { + get_transaction: { parameters: { query?: { - /** @description Number of results per page */ - limit?: number; - /** @description Cursor for paginating mempool transactions. Format: receipt_time:tx_id */ - cursor?: string; + /** @description Heavy fields to include in the response. Omitted by default to keep the payload lean. Provide as repeated querystring values (`?include=A&include=B`) or as a single comma-separated value (`?include=A,B`). */ + include?: ("function_args" | "source_code" | "post_conditions" | "result")[]; }; header?: never; - path?: never; + path: { + /** + * @description Transaction ID + * @example 0xf6bd5f4a7b26184a3466340b2e99fd003b4962c0e382a7e4b6a13df3dd7a91c6 + */ + tx_id: string; + }; cookie?: never; }; requestBody?: never; @@ -32410,134 +32460,1845 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": { - /** @example 1 */ - total: number; - /** - * @description Number of results per page - * @default 20 - */ - limit: number; - cursor: { - next: string | null; - previous: string | null; - current: string | null; + "application/json": ({ + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; }; - results: ({ - /** @description Transaction ID */ - tx_id: string; - sender: { - /** @description Address of the transaction initiator */ + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - }; - sponsor: { - /** @description Address of the transaction initiator */ + } | { + /** @enum {string} */ + type_id: "principal_contract"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - } | null; - /** @description Transaction fee as Integer string (64-bit unsigned integer). */ - fee_rate: string; - /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ - receipt_time: number; - /** @description Height of the block this transaction was received by the node */ - receipt_block_height: number; - /** @description Status of the mempool transaction */ - status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; - /** @enum {string} */ - type: "token_transfer"; - token_transfer: { - recipient: string; - /** @description Transfer amount as Integer string (64-bit unsigned integer) */ - amount: string; - memo: string | null; + contract_name: string; }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; } | { - /** @description Transaction ID */ - tx_id: string; - sender: { - /** @description Address of the transaction initiator */ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - }; - sponsor: { - /** @description Address of the transaction initiator */ + } | { + /** @enum {string} */ + type_id: "principal_contract"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - } | null; - /** @description Transaction fee as Integer string (64-bit unsigned integer). */ - fee_rate: string; - /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ - receipt_time: number; - /** @description Height of the block this transaction was received by the node */ - receipt_block_height: number; - /** @description Status of the mempool transaction */ - status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; /** @enum {string} */ - type: "smart_contract"; - smart_contract: { - clarity_version: number | null; - /** @description Contract identifier formatted as `.` */ - contract_id: string; + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; }; } | { - /** @description Transaction ID */ - tx_id: string; - sender: { - /** @description Address of the transaction initiator */ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - }; - sponsor: { - /** @description Address of the transaction initiator */ + } | { + /** @enum {string} */ + type_id: "principal_contract"; address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; - } | null; - /** @description Transaction fee as Integer string (64-bit unsigned integer). */ - fee_rate: string; - /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ - receipt_time: number; - /** @description Height of the block this transaction was received by the node */ - receipt_block_height: number; - /** @description Status of the mempool transaction */ - status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; /** @enum {string} */ - type: "contract_call"; - contract_call: { - /** @description Contract identifier formatted as `.` */ - contract_id: string; - /** @description Name of the Clarity function to be invoked */ - function_name: string; + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; }; - } | { - /** @description Transaction ID */ - tx_id: string; - sender: { - /** @description Address of the transaction initiator */ - address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; }; - sponsor: { - /** @description Address of the transaction initiator */ - address: string; - /** @description Nonce of the transaction initiator */ - nonce: number; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "token_transfer"; + token_transfer: { + /** @description Recipient of the token transfer */ + recipient: string; + /** @description Amount of the token transfer */ + amount: string; + memo: { + hex: string; + repr: string; } | null; - /** @description Transaction fee as Integer string (64-bit unsigned integer). */ - fee_rate: string; - /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ - receipt_time: number; - /** @description Height of the block this transaction was received by the node */ - receipt_block_height: number; - /** @description Status of the mempool transaction */ - status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "smart_contract"; + smart_contract: { + /** @description Contract ID of the smart contract */ + contract_id: string; + clarity_version: number | null; + /** @description Source code of the smart contract. Only present when requested via the `include=source_code` query param. */ + source_code?: string; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "contract_call"; + contract_call: { + /** @description Contract ID of the contract call */ + contract_id: string; + /** @description Function name of the contract call */ + function_name: string; + /** @description List of arguments used to invoke the function. Only present when requested via the `include=function_args` query param. */ + function_args?: { + hex: string; + repr: string; + }[]; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "poison_microblock"; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "tenure_change"; + tenure_change: { + /** @description Consensus hash of this tenure. Corresponds to the sortition in which the miner of this block was chosen. */ + tenure_consensus_hash: string; + /** @description Consensus hash of the previous tenure. Corresponds to the sortition of the previous winning block-commit. */ + prev_tenure_consensus_hash: string; + /** @description Current consensus hash on the underlying burnchain. Corresponds to the last-seen sortition. */ + burn_view_consensus_hash: string; + /** @description (Hex string) Stacks Block hash */ + previous_tenure_end: string; + /** @description The number of blocks produced in the previous tenure. */ + previous_tenure_blocks: number; + /** @description Cause of change in mining tenure. Depending on cause, tenure can be ended or extended. */ + cause: "block_found" | "extended" | "extended_runtime" | "extended_read_count" | "extended_read_length" | "extended_write_count" | "extended_write_length"; + /** @description (Hex string) The ECDSA public key hash of the current tenure. */ + pubkey_hash: string; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + block: { + /** @description Height of the block this transactions was associated with */ + height: number; + /** @description Hash of the blocked this transactions was associated with */ + hash: string; + /** @description Hash of the index block this transactions was associated with */ + index_hash: string; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + /** @description Index of the transaction, indicating the order. Starts at `0` and increases with each transaction */ + tx_index: number; + }; + bitcoin_block: { + /** @description Height of the anchor burn block. */ + height: number; + /** @description Unix timestamp (in seconds) indicating when this block was mined. */ + time: number; + }; + /** @description Status of the transaction */ + status: "success" | "abort_by_response" | "abort_by_post_condition"; + parent_block: { + /** @description Hash of the parent block */ + hash: string; + /** @description Index block hash of the parent block */ + index_hash: string; + }; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + /** @description Number of events in the transaction */ + event_count: number; + execution_cost: { + /** @description Number of reads in the transaction */ + read_count: number; + /** @description Length of reads in the transaction */ + read_length: number; + /** @description Runtime of the transaction */ + runtime: number; + /** @description Number of writes in the transaction */ + write_count: number; + /** @description Length of writes in the transaction */ + write_length: number; + }; + vm_error: string | null; + result?: { + hex: string; + repr: string; + }; + /** @enum {string} */ + type: "coinbase"; + coinbase: { + /** @description Payload of the coinbase transaction */ + payload: string; + alt_recipient: string | null; + vrf_proof: string | null; + }; + }) | ({ + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "token_transfer"; + token_transfer: { + /** @description Recipient of the token transfer */ + recipient: string; + /** @description Amount of the token transfer */ + amount: string; + memo: { + hex: string; + repr: string; + } | null; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "smart_contract"; + smart_contract: { + /** @description Contract ID of the smart contract */ + contract_id: string; + clarity_version: number | null; + /** @description Source code of the smart contract. Only present when requested via the `include=source_code` query param. */ + source_code?: string; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "contract_call"; + contract_call: { + /** @description Contract ID of the contract call */ + contract_id: string; + /** @description Function name of the contract call */ + function_name: string; + /** @description List of arguments used to invoke the function. Only present when requested via the `include=function_args` query param. */ + function_args?: { + hex: string; + repr: string; + }[]; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "poison_microblock"; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "tenure_change"; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @description Only present when requested via the `include=post_conditions` query param. */ + post_conditions?: ({ + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "stx"; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent_equal_to" | "sent_greater_than" | "sent_greater_than_or_equal_to" | "sent_less_than" | "sent_less_than_or_equal_to"; + amount: string; + /** @enum {string} */ + type: "fungible"; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + } | { + principal: { + /** @enum {string} */ + type_id: "principal_origin"; + } | { + /** @enum {string} */ + type_id: "principal_standard"; + address: string; + } | { + /** @enum {string} */ + type_id: "principal_contract"; + address: string; + contract_name: string; + }; + condition_code: "sent" | "not_sent" | "maybe_sent"; + /** @enum {string} */ + type: "non_fungible"; + asset_value: { + hex: string; + repr: string; + }; + asset: { + asset_name: string; + contract_address: string; + contract_name: string; + }; + })[]; + replaced_by_tx_id: string | null; + /** @enum {string} */ + type: "coinbase"; + }); + }; + }; + /** @description Default Response */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message?: string; + } & { + [key: string]: unknown; + }; + }; + }; + }; + }; + get_transaction_events: { + parameters: { + query?: { + /** @description Number of results per page */ + limit?: number; + /** @description Cursor for paginating transaction events. Format: event_index */ + cursor?: string; + }; + header?: never; + path: { + /** + * @description Transaction ID + * @example 0xf6bd5f4a7b26184a3466340b2e99fd003b4962c0e382a7e4b6a13df3dd7a91c6 + */ + tx_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example 1 */ + total: number; + /** + * @description Number of results per page + * @default 20 + */ + limit: number; + cursor: { + next: string | null; + previous: string | null; + current: string | null; + }; + results: ({ + event_index: number; + /** @enum {string} */ + type: "contract_log"; + contract_log: { + /** + * Smart Contract ID + * @description Smart Contract ID + */ + contract_id: string; + /** @enum {string} */ + topic: "print"; + value: { + hex: string; + repr: string; + }; + }; + } | { + event_index: number; + /** @enum {string} */ + type: "stx_lock"; + stx_lock: { + /** + * Amount + * @description Amount + */ + amount: string; + /** + * Block height + * @description Block height + */ + unlock_bitcoin_height: number; + address: string; + }; + } | { + event_index: number; + /** @enum {string} */ + type: "stx_asset"; + stx_asset: { + /** @enum {string} */ + type: "transfer"; + sender: string; + recipient: string; + /** + * Amount + * @description Amount + */ + amount: string; + memo: { + hex: string; + repr: string; + } | null; + } | { + /** @enum {string} */ + type: "mint"; + recipient: string; + /** + * Amount + * @description Amount + */ + amount: string; + } | { + /** @enum {string} */ + type: "burn"; + sender: string; + /** + * Amount + * @description Amount + */ + amount: string; + }; + } | { + event_index: number; + /** @enum {string} */ + type: "ft_asset"; + ft_asset: { + /** @enum {string} */ + type: "transfer"; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + sender: string; + recipient: string; + /** + * Amount + * @description Amount + */ + amount: string; + } | { + /** @enum {string} */ + type: "mint"; + recipient: string; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + /** + * Amount + * @description Amount + */ + amount: string; + } | { + /** @enum {string} */ + type: "burn"; + sender: string; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + /** + * Amount + * @description Amount + */ + amount: string; + }; + } | { + event_index: number; + /** @enum {string} */ + type: "nft_asset"; + nft_asset: { + /** @enum {string} */ + type: "transfer"; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + sender: string; + recipient: string; + value: { + hex: string; + repr: string; + }; + } | { + /** @enum {string} */ + type: "mint"; + recipient: string; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + value: { + hex: string; + repr: string; + }; + } | { + /** @enum {string} */ + type: "burn"; + sender: string; + /** + * Asset Identifier + * @description Asset Identifier + */ + asset_identifier: string; + value: { + hex: string; + repr: string; + }; + }; + })[]; + }; + }; + }; + /** @description Default Response */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message?: string; + } & { + [key: string]: unknown; + }; + }; + }; + }; + }; + get_mempool_transactions: { + parameters: { + query?: { + /** @description Number of results per page */ + limit?: number; + /** @description Cursor for paginating mempool transactions. Format: receipt_time:tx_id */ + cursor?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example 1 */ + total: number; + /** + * @description Number of results per page + * @default 20 + */ + limit: number; + cursor: { + next: string | null; + previous: string | null; + current: string | null; + }; + results: ({ + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @enum {string} */ + type: "token_transfer"; + token_transfer: { + recipient: string; + /** @description Transfer amount as Integer string (64-bit unsigned integer) */ + amount: string; + memo: { + hex: string; + repr: string; + } | null; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @enum {string} */ + type: "smart_contract"; + smart_contract: { + clarity_version: number | null; + /** @description Contract identifier formatted as `.` */ + contract_id: string; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; + /** @enum {string} */ + type: "contract_call"; + contract_call: { + /** @description Contract identifier formatted as `.` */ + contract_id: string; + /** @description Name of the Clarity function to be invoked */ + function_name: string; + }; + } | { + /** @description Transaction ID */ + tx_id: string; + sender: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + }; + sponsor: { + /** @description Address of the transaction initiator */ + address: string; + /** @description Nonce of the transaction initiator */ + nonce: number; + } | null; + /** @description Transaction fee as Integer string (64-bit unsigned integer). */ + fee_rate: string; + /** @description A unix timestamp (in seconds) indicating when the transaction broadcast was received by the node. */ + receipt_time: number; + /** @description Height of the block this transaction was received by the node */ + receipt_block_height: number; + /** @description Status of the mempool transaction */ + status: "pending" | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" | "dropped_stale_garbage_collect" | "dropped_problematic"; /** @enum {string} */ type: "poison_microblock"; } | { @@ -32688,7 +34449,10 @@ export interface operations { recipient: string; /** @description Transfer amount as Integer string (64-bit unsigned integer) */ amount: string; - memo: string | null; + memo: { + hex: string; + repr: string; + } | null; }; } | { /** @description Transaction ID */ diff --git a/migrations/1775100000000_tx-event-pagination-indexes.ts b/migrations/1775100000000_tx-event-pagination-indexes.ts new file mode 100644 index 0000000000..04f4d41410 --- /dev/null +++ b/migrations/1775100000000_tx-event-pagination-indexes.ts @@ -0,0 +1,54 @@ +import type { MigrationBuilder } from 'node-pg-migrate'; + +const CANONICAL_EVENT_WHERE = 'canonical = TRUE AND microblock_canonical = TRUE'; + +export const up = (pgm: MigrationBuilder) => { + pgm.createIndex('stx_events', ['tx_id', 'event_index'], { + name: 'stx_events_canonical_tx_id_event_index_idx', + where: CANONICAL_EVENT_WHERE, + ifNotExists: true, + }); + pgm.createIndex('ft_events', ['tx_id', 'event_index'], { + name: 'ft_events_canonical_tx_id_event_index_idx', + where: CANONICAL_EVENT_WHERE, + ifNotExists: true, + }); + pgm.createIndex('nft_events', ['tx_id', 'event_index'], { + name: 'nft_events_canonical_tx_id_event_index_idx', + where: CANONICAL_EVENT_WHERE, + ifNotExists: true, + }); + pgm.createIndex('stx_lock_events', ['tx_id', 'event_index'], { + name: 'stx_lock_events_canonical_tx_id_event_index_idx', + where: CANONICAL_EVENT_WHERE, + ifNotExists: true, + }); + pgm.createIndex('contract_logs', ['tx_id', 'event_index'], { + name: 'contract_logs_canonical_tx_id_event_index_idx', + where: CANONICAL_EVENT_WHERE, + ifNotExists: true, + }); +}; + +export const down = (pgm: MigrationBuilder) => { + pgm.dropIndex('stx_events', [], { + name: 'stx_events_canonical_tx_id_event_index_idx', + ifExists: true, + }); + pgm.dropIndex('ft_events', [], { + name: 'ft_events_canonical_tx_id_event_index_idx', + ifExists: true, + }); + pgm.dropIndex('nft_events', [], { + name: 'nft_events_canonical_tx_id_event_index_idx', + ifExists: true, + }); + pgm.dropIndex('stx_lock_events', [], { + name: 'stx_lock_events_canonical_tx_id_event_index_idx', + ifExists: true, + }); + pgm.dropIndex('contract_logs', [], { + name: 'contract_logs_canonical_tx_id_event_index_idx', + ifExists: true, + }); +}; diff --git a/migrations/1779392293803_pox5-events.ts b/migrations/1779392293803_pox5-events.ts new file mode 100644 index 0000000000..d7634f1c59 --- /dev/null +++ b/migrations/1779392293803_pox5-events.ts @@ -0,0 +1,88 @@ +import type { MigrationBuilder } from 'node-pg-migrate'; + +export const up = (pgm: MigrationBuilder) => { + pgm.createTable('pox5_events', { + id: { + type: 'bigserial', + primaryKey: true, + }, + event_index: { + type: 'integer', + notNull: true, + }, + tx_id: { + notNull: true, + type: 'bytea', + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + name: { + type: 'text', + notNull: true, + }, + data: { + type: 'jsonb', + notNull: true, + }, + }); + + pgm.createIndex( + 'pox5_events', + [ + { name: 'block_height', sort: 'DESC' }, + { name: 'microblock_sequence', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + { name: 'event_index', sort: 'DESC' }, + ], + { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + } + ); + pgm.createIndex('pox5_events', 'tx_id'); + pgm.createIndex('pox5_events', ['index_block_hash', 'canonical']); + pgm.createIndex('pox5_events', 'microblock_hash'); + + // Add pox_v5_unlock_height to pox_state table to track the unlock height for pox v5 + pgm.addColumn('pox_state', { + pox_v4_unlock_height: { + type: 'bigint', + notNull: true, + default: 0, + }, + }); +}; + +export const down = (pgm: MigrationBuilder) => { + pgm.dropTable('pox5_events'); + pgm.dropColumn('pox_state', 'pox_v4_unlock_height'); +}; diff --git a/migrations/1779487960678_bonds.ts b/migrations/1779487960678_bonds.ts new file mode 100644 index 0000000000..de829384c0 --- /dev/null +++ b/migrations/1779487960678_bonds.ts @@ -0,0 +1,145 @@ +import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export const up = (pgm: MigrationBuilder) => { + pgm.createTable('bonds', { + id: { + type: 'bigserial', + primaryKey: true, + }, + tx_id: { + type: 'bytea', + notNull: true, + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + bond_index: { + type: 'integer', + notNull: true, + }, + target_rate: { + type: 'integer', + notNull: true, + }, + stx_value_ratio: { + type: 'integer', + notNull: true, + }, + min_ustx_ratio: { + type: 'integer', + notNull: true, + }, + early_unlock_bytes: { + type: 'text', + notNull: true, + }, + early_unlock_admin: { + type: 'text', + notNull: true, + }, + first_reward_cycle: { + type: 'integer', + notNull: true, + }, + bond_start_height: { + type: 'integer', + notNull: true, + }, + unlock_cycle: { + type: 'integer', + notNull: true, + }, + unlock_burn_height: { + type: 'integer', + notNull: true, + }, + btc_capacity: { + type: 'numeric', + notNull: true, + }, + btc_locked: { + type: 'numeric', + notNull: true, + default: 0, + }, + stx_locked: { + type: 'numeric', + notNull: true, + default: 0, + }, + btc_paid_out: { + type: 'numeric', + notNull: true, + default: 0, + }, + allowed_count: { + type: 'integer', + notNull: true, + default: 0, + }, + registered_count: { + type: 'integer', + notNull: true, + default: 0, + }, + }); + pgm.createIndex( + 'bonds', + [ + { name: 'block_height', sort: 'DESC' }, + { name: 'microblock_sequence', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + ], + { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + } + ); + pgm.createIndex('bonds', 'bond_index', { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + }); + + // Add bond count to chain tip table to track the number of bonds in the chain + pgm.addColumn('chain_tip', { + bond_count: { + type: 'integer', + notNull: true, + default: 0, + }, + }); +}; + +export const down = (pgm: MigrationBuilder) => { + pgm.dropTable('bonds'); + pgm.dropColumn('chain_tip', 'bond_count'); +}; diff --git a/migrations/1779487971367_bond-allowlist-entries.ts b/migrations/1779487971367_bond-allowlist-entries.ts new file mode 100644 index 0000000000..2d0df0ea7c --- /dev/null +++ b/migrations/1779487971367_bond-allowlist-entries.ts @@ -0,0 +1,82 @@ +import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export const up = (pgm: MigrationBuilder) => { + pgm.createTable('bond_allowlist_entries', { + id: { + type: 'bigserial', + primaryKey: true, + }, + tx_id: { + type: 'bytea', + notNull: true, + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + bond_index: { + type: 'integer', + notNull: true, + }, + staker: { + type: 'string', + notNull: true, + }, + max_sats: { + type: 'string', + notNull: true, + }, + }); + + pgm.createIndex( + 'bond_allowlist_entries', + [ + { name: 'block_height', sort: 'DESC' }, + { name: 'microblock_sequence', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + ], + { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + } + ); + pgm.createIndex('bond_allowlist_entries', 'bond_index', { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + }); + pgm.createIndex('bond_allowlist_entries', 'staker', { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + }); +}; + +export const down = (pgm: MigrationBuilder) => { + pgm.dropTable('bond_allowlist_entries'); +}; diff --git a/migrations/1779487975550_bond-registrations.ts b/migrations/1779487975550_bond-registrations.ts new file mode 100644 index 0000000000..d8ad35bede --- /dev/null +++ b/migrations/1779487975550_bond-registrations.ts @@ -0,0 +1,105 @@ +import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export const up = (pgm: MigrationBuilder) => { + pgm.createTable('bond_registrations', { + id: { + type: 'bigserial', + primaryKey: true, + }, + tx_id: { + type: 'bytea', + notNull: true, + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + bond_index: { + type: 'integer', + notNull: true, + }, + signer: { + type: 'text', + notNull: true, + }, + staker: { + type: 'text', + notNull: true, + }, + amount_ustx: { + type: 'text', + notNull: true, + }, + sats_total: { + type: 'text', + notNull: true, + }, + first_reward_cycle: { + type: 'integer', + notNull: true, + }, + unlock_burn_height: { + type: 'integer', + notNull: true, + }, + unlock_cycle: { + type: 'integer', + notNull: true, + }, + is_l1_lock: { + type: 'boolean', + notNull: true, + }, + }); + + pgm.createIndex( + 'bond_registrations', + [ + 'bond_index', + { name: 'block_height', sort: 'DESC' }, + { name: 'microblock_sequence', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + { name: 'id', sort: 'DESC' }, + ], + { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + } + ); + pgm.createIndex('bond_registrations', ['bond_index', 'staker'], { + where: 'canonical = TRUE AND microblock_canonical = TRUE', + }); +}; + +export const down = (pgm: MigrationBuilder) => { + pgm.dropTable('bond_registrations'); +}; diff --git a/migrations/1779742831642_principal-bond-positions.ts b/migrations/1779742831642_principal-bond-positions.ts new file mode 100644 index 0000000000..ada0524861 --- /dev/null +++ b/migrations/1779742831642_principal-bond-positions.ts @@ -0,0 +1,84 @@ +import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.createTable('principal_bond_positions', { + id: { + type: 'bigserial', + primaryKey: true, + }, + principal: { + type: 'string', + notNull: true, + }, + bond_index: { + type: 'integer', + notNull: true, + }, + tx_id: { + type: 'bytea', + notNull: true, + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + status: { + type: 'smallint', + notNull: true, + }, + active: { + type: 'boolean', + notNull: true, + }, + btc_locked: { + type: 'numeric', + notNull: true, + }, + stx_locked: { + type: 'numeric', + notNull: true, + }, + btc_paid_out: { + type: 'numeric', + notNull: true, + }, + }); + + pgm.createIndex('principal_bond_positions', ['principal', 'bond_index'], { + unique: true, + }); +} + +export function down(pgm: MigrationBuilder): void { + pgm.dropTable('principal_bond_positions'); +} diff --git a/migrations/1779745340752_bond-reward-distributions.ts b/migrations/1779745340752_bond-reward-distributions.ts new file mode 100644 index 0000000000..4f427b1246 --- /dev/null +++ b/migrations/1779745340752_bond-reward-distributions.ts @@ -0,0 +1,80 @@ +import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.createTable('bond_reward_distributions', { + bond_index: { + type: 'integer', + notNull: true, + }, + tx_id: { + type: 'bytea', + notNull: true, + }, + tx_index: { + type: 'smallint', + notNull: true, + }, + block_height: { + type: 'integer', + notNull: true, + }, + index_block_hash: { + type: 'bytea', + notNull: true, + }, + parent_index_block_hash: { + type: 'bytea', + notNull: true, + }, + microblock_hash: { + type: 'bytea', + notNull: true, + }, + microblock_sequence: { + type: 'integer', + notNull: true, + }, + microblock_canonical: { + type: 'boolean', + notNull: true, + }, + canonical: { + type: 'boolean', + notNull: true, + }, + remaining_rewards: { + type: 'numeric', + notNull: true, + }, + accrued_rewards: { + type: 'numeric', + notNull: true, + }, + new_reserve: { + type: 'numeric', + notNull: true, + }, + stx_staker_rewards: { + type: 'numeric', + notNull: true, + }, + stx_cycle: { + type: 'integer', + notNull: true, + }, + cycle_staked_ustx: { + type: 'numeric', + notNull: true, + }, + next_rewards_per_ustx: { + type: 'numeric', + notNull: true, + } + }); +} + +export function down(pgm: MigrationBuilder): void { + pgm.dropTable('bond_reward_distributions'); +} diff --git a/openapi.yaml b/openapi.yaml index f85fdefb2b..380dbe688b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -91939,9 +91939,15 @@ paths: type: string memo: anyOf: - - description: Hex encoded arbitrary message, up to 34 bytes length (should try - decoding to an ASCII string) - type: string + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string - type: "null" - title: SmartContractTransactionSummary description: Smart contract transaction summary @@ -92771,9 +92777,15 @@ paths: type: string memo: anyOf: - - description: Hex encoded arbitrary message, up to 34 bytes length (should try - decoding to an ASCII string) - type: string + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string - type: "null" - title: SmartContractTransactionSummary description: Smart contract transaction summary @@ -93358,164 +93370,5258 @@ paths: type: string message: type: string - /extended/v3/mempool/transactions: + /extended/v3/transactions/{tx_id}: get: - operationId: get_mempool_transactions - summary: Get mempool transactions + operationId: get_transaction + summary: Get transaction tags: - - Mempool - description: Retrieves a list of recently broadcasted transactions + - Transactions + description: Retrieves details for a given transaction, including both mined and + mempool transactions parameters: - schema: - minimum: 1 - default: 20 - maximum: 50 - type: integer + uniqueItems: true + type: array + items: + anyOf: + - type: string + enum: + - function_args + - type: string + enum: + - source_code + - type: string + enum: + - post_conditions + - type: string + enum: + - result in: query - name: limit + name: include required: false - description: Number of results per page + description: Heavy fields to include in the response. Omitted by default to keep + the payload lean. Provide as repeated querystring values + (`?include=A&include=B`) or as a single comma-separated value + (`?include=A,B`). - schema: - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ + pattern: ^(0x)?[a-fA-F0-9]{64}$ + title: Transaction ID type: string - in: query - name: cursor - required: false - description: "Cursor for paginating mempool transactions. Format: - receipt_time:tx_id" + example: "0xf6bd5f4a7b26184a3466340b2e99fd003b4962c0e382a7e4b6a13df3dd7a91c6" + in: path + name: tx_id + required: true + description: Transaction ID responses: "200": description: Default Response content: application/json: schema: - type: object - required: - - total - - limit - - cursor - - results - properties: - total: - type: integer - example: 1 - limit: - minimum: 1 - default: 20 - maximum: 50 - description: Number of results per page - type: integer - cursor: - type: object - required: - - next - - previous - - current - properties: - next: - anyOf: - - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ - description: "Cursor for paginating mempool transactions. Format: - receipt_time:tx_id" - type: string - - type: "null" - previous: - anyOf: - - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ - description: "Cursor for paginating mempool transactions. Format: - receipt_time:tx_id" + anyOf: + - anyOf: + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + - token_transfer + properties: + tx_id: + description: Transaction ID type: string - - type: "null" - current: - anyOf: - - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ - description: "Cursor for paginating mempool transactions. Format: - receipt_time:tx_id" + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). type: string - - type: "null" - results: - type: array - items: - anyOf: - - title: TokenTransferMempoolTransactionSummary - description: Token transfer mempool transaction summary - type: object - required: - - tx_id - - sender - - sponsor - - fee_rate - - receipt_time - - receipt_block_height - - status - - type - - token_transfer - properties: - tx_id: - description: Transaction ID - type: string - sender: - type: object - required: - - address - - nonce - properties: - address: - description: Address of the transaction initiator - type: string - nonce: - description: Nonce of the transaction initiator - type: integer - sponsor: + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: anyOf: - type: object required: - - address - - nonce + - principal + - condition_code + - amount + - type properties: - address: - description: Address of the transaction initiator - type: string - nonce: - description: Nonce of the transaction initiator - type: integer - - type: "null" - fee_rate: - description: Transaction fee as Integer string (64-bit unsigned integer). - type: string - receipt_time: - description: A unix timestamp (in seconds) indicating when the transaction - broadcast was received by the node. - type: integer - receipt_block_height: - description: Height of the block this transaction was received by the node - type: integer - status: - description: Status of the mempool transaction - anyOf: - - type: string - enum: - - pending - - type: string - enum: - - dropped_replace_by_fee - - type: string - enum: - - dropped_replace_across_fork - - type: string - enum: - - dropped_too_expensive - - type: string - enum: - - dropped_stale_garbage_collect - - type: string - enum: - - dropped_problematic - type: - type: string - enum: - - token_transfer - token_transfer: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - token_transfer + token_transfer: + type: object + required: + - recipient + - amount + - memo + properties: + recipient: + description: Recipient of the token transfer + type: string + amount: + description: Amount of the token transfer + type: string + memo: + anyOf: + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: "null" + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + - smart_contract + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - smart_contract + smart_contract: + type: object + required: + - contract_id + - clarity_version + properties: + contract_id: + description: Contract ID of the smart contract + type: string + clarity_version: + anyOf: + - description: Clarity version of the smart contract + type: number + - type: "null" + source_code: + description: Source code of the smart contract. Only present when requested via + the `include=source_code` query param. + type: string + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + - contract_call + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - contract_call + contract_call: + type: object + required: + - contract_id + - function_name + properties: + contract_id: + description: Contract ID of the contract call + type: string + function_name: + description: Function name of the contract call + type: string + function_args: + description: List of arguments used to invoke the function. Only present when + requested via the `include=function_args` + query param. + type: array + items: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - poison_microblock + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + - tenure_change + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - tenure_change + tenure_change: + type: object + required: + - tenure_consensus_hash + - prev_tenure_consensus_hash + - burn_view_consensus_hash + - previous_tenure_end + - previous_tenure_blocks + - cause + - pubkey_hash + properties: + tenure_consensus_hash: + description: Consensus hash of this tenure. Corresponds to the sortition in + which the miner of this block was chosen. + type: string + prev_tenure_consensus_hash: + description: Consensus hash of the previous tenure. Corresponds to the sortition + of the previous winning block-commit. + type: string + burn_view_consensus_hash: + description: Current consensus hash on the underlying burnchain. Corresponds to + the last-seen sortition. + type: string + previous_tenure_end: + description: (Hex string) Stacks Block hash + type: string + previous_tenure_blocks: + description: The number of blocks produced in the previous tenure. + type: integer + cause: + description: Cause of change in mining tenure. Depending on cause, tenure can be + ended or extended. + anyOf: + - type: string + enum: + - block_found + - type: string + enum: + - extended + - type: string + enum: + - extended_runtime + - type: string + enum: + - extended_read_count + - type: string + enum: + - extended_read_length + - type: string + enum: + - extended_write_count + - type: string + enum: + - extended_write_length + pubkey_hash: + description: (Hex string) The ECDSA public key hash of the current tenure. + type: string + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - block + - bitcoin_block + - status + - parent_block + - event_count + - execution_cost + - vm_error + - type + - coinbase + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + block: + type: object + required: + - height + - hash + - index_hash + - time + - tx_index + properties: + height: + description: Height of the block this transactions was associated with + type: integer + hash: + description: Hash of the blocked this transactions was associated with + type: string + index_hash: + description: Hash of the index block this transactions was associated with + type: string + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + tx_index: + description: Index of the transaction, indicating the order. Starts at `0` and + increases with each transaction + type: integer + bitcoin_block: + type: object + required: + - height + - time + properties: + height: + description: Height of the anchor burn block. + type: integer + time: + description: Unix timestamp (in seconds) indicating when this block was mined. + type: number + status: + description: Status of the transaction + anyOf: + - type: string + enum: + - success + - type: string + enum: + - abort_by_response + - type: string + enum: + - abort_by_post_condition + parent_block: + type: object + required: + - hash + - index_hash + properties: + hash: + description: Hash of the parent block + type: string + index_hash: + description: Index block hash of the parent block + type: string + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + event_count: + description: Number of events in the transaction + type: integer + execution_cost: + type: object + required: + - read_count + - read_length + - runtime + - write_count + - write_length + properties: + read_count: + description: Number of reads in the transaction + type: integer + read_length: + description: Length of reads in the transaction + type: integer + runtime: + description: Runtime of the transaction + type: integer + write_count: + description: Number of writes in the transaction + type: integer + write_length: + description: Length of writes in the transaction + type: integer + vm_error: + anyOf: + - description: VM error of the transaction + type: string + - type: "null" + result: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + type: + type: string + enum: + - coinbase + coinbase: + type: object + required: + - payload + - alt_recipient + - vrf_proof + properties: + payload: + description: Payload of the coinbase transaction + type: string + alt_recipient: + anyOf: + - description: Alt recipient of the coinbase transaction + type: string + - type: "null" + vrf_proof: + anyOf: + - description: VRF proof of the coinbase transaction + type: string + - type: "null" + - anyOf: + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + - token_transfer + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - token_transfer + token_transfer: + type: object + required: + - recipient + - amount + - memo + properties: + recipient: + description: Recipient of the token transfer + type: string + amount: + description: Amount of the token transfer + type: string + memo: + anyOf: + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: "null" + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + - smart_contract + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - smart_contract + smart_contract: + type: object + required: + - contract_id + - clarity_version + properties: + contract_id: + description: Contract ID of the smart contract + type: string + clarity_version: + anyOf: + - description: Clarity version of the smart contract + type: number + - type: "null" + source_code: + description: Source code of the smart contract. Only present when requested via + the `include=source_code` query param. + type: string + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + - contract_call + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - contract_call + contract_call: + type: object + required: + - contract_id + - function_name + properties: + contract_id: + description: Contract ID of the contract call + type: string + function_name: + description: Function name of the contract call + type: string + function_args: + description: List of arguments used to invoke the function. Only present when + requested via the `include=function_args` + query param. + type: array + items: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - poison_microblock + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - tenure_change + - type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - replaced_by_tx_id + - type + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + post_conditions: + description: Only present when requested via the `include=post_conditions` query + param. + type: array + items: + anyOf: + - type: object + required: + - principal + - condition_code + - amount + - type + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - stx + - type: object + required: + - principal + - condition_code + - amount + - type + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent_equal_to + - type: string + enum: + - sent_greater_than + - type: string + enum: + - sent_greater_than_or_equal_to + - type: string + enum: + - sent_less_than + - type: string + enum: + - sent_less_than_or_equal_to + amount: + type: string + type: + type: string + enum: + - fungible + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + - type: object + required: + - principal + - condition_code + - type + - asset_value + - asset + properties: + principal: + anyOf: + - type: object + required: + - type_id + properties: + type_id: + type: string + enum: + - principal_origin + - type: object + required: + - type_id + - address + properties: + type_id: + type: string + enum: + - principal_standard + address: + type: string + - type: object + required: + - type_id + - address + - contract_name + properties: + type_id: + type: string + enum: + - principal_contract + address: + type: string + contract_name: + type: string + condition_code: + anyOf: + - type: string + enum: + - sent + - type: string + enum: + - not_sent + - type: string + enum: + - maybe_sent + type: + type: string + enum: + - non_fungible + asset_value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + asset: + type: object + required: + - asset_name + - contract_address + - contract_name + properties: + asset_name: + type: string + contract_address: + type: string + contract_name: + type: string + replaced_by_tx_id: + anyOf: + - description: ID of another transaction which replaced this one + type: string + - type: "null" + type: + type: string + enum: + - coinbase + 4XX: + description: Default Response + content: + application/json: + schema: + title: Error Response + additionalProperties: true + type: object + required: + - error + properties: + error: + type: string + message: + type: string + /extended/v3/transactions/{tx_id}/events: + get: + operationId: get_transaction_events + summary: Get transaction events + tags: + - Transactions + description: Retrieves events for a given transaction ID + parameters: + - schema: + minimum: 1 + default: 20 + maximum: 100 + type: integer + in: query + name: limit + required: false + description: Number of results per page + - schema: + pattern: ^[0-9]+$ + type: string + in: query + name: cursor + required: false + description: "Cursor for paginating transaction events. Format: event_index" + - schema: + pattern: ^(0x)?[a-fA-F0-9]{64}$ + title: Transaction ID + type: string + example: "0xf6bd5f4a7b26184a3466340b2e99fd003b4962c0e382a7e4b6a13df3dd7a91c6" + in: path + name: tx_id + required: true + description: Transaction ID + responses: + "200": + description: Default Response + content: + application/json: + schema: + type: object + required: + - total + - limit + - cursor + - results + properties: + total: + type: integer + example: 1 + limit: + minimum: 1 + default: 20 + maximum: 100 + description: Number of results per page + type: integer + cursor: + type: object + required: + - next + - previous + - current + properties: + next: + anyOf: + - pattern: ^[0-9]+$ + description: "Cursor for paginating transaction events. Format: event_index" + type: string + - type: "null" + previous: + anyOf: + - pattern: ^[0-9]+$ + description: "Cursor for paginating transaction events. Format: event_index" + type: string + - type: "null" + current: + anyOf: + - pattern: ^[0-9]+$ + description: "Cursor for paginating transaction events. Format: event_index" + type: string + - type: "null" + results: + type: array + items: + anyOf: + - type: object + required: + - event_index + - type + - contract_log + properties: + event_index: + type: integer + type: + type: string + enum: + - contract_log + contract_log: + type: object + required: + - contract_id + - topic + - value + properties: + contract_id: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + topic: + type: string + enum: + - print + value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: object + required: + - event_index + - type + - stx_lock + properties: + event_index: + type: integer + type: + type: string + enum: + - stx_lock + stx_lock: + type: object + required: + - amount + - unlock_bitcoin_height + - address + properties: + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + unlock_bitcoin_height: + title: Block height + description: Block height + examples: + - 777678 + type: integer + address: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + - type: object + required: + - event_index + - type + - stx_asset + properties: + event_index: + type: integer + type: + type: string + enum: + - stx_asset + stx_asset: + anyOf: + - type: object + required: + - type + - sender + - recipient + - amount + - memo + properties: + type: + type: string + enum: + - transfer + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + memo: + anyOf: + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: "null" + - type: object + required: + - type + - recipient + - amount + properties: + type: + type: string + enum: + - mint + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + - type: object + required: + - type + - sender + - amount + properties: + type: + type: string + enum: + - burn + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + - type: object + required: + - event_index + - type + - ft_asset + properties: + event_index: + type: integer + type: + type: string + enum: + - ft_asset + ft_asset: + anyOf: + - type: object + required: + - type + - asset_identifier + - sender + - recipient + - amount + properties: + type: + type: string + enum: + - transfer + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + - type: object + required: + - type + - recipient + - asset_identifier + - amount + properties: + type: + type: string + enum: + - mint + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + - type: object + required: + - type + - sender + - asset_identifier + - amount + properties: + type: + type: string + enum: + - burn + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + amount: + pattern: ^[0-9]+$ + title: Amount + description: Amount + examples: + - "1000000" + type: string + - type: object + required: + - event_index + - type + - nft_asset + properties: + event_index: + type: integer + type: + type: string + enum: + - nft_asset + nft_asset: + anyOf: + - type: object + required: + - type + - asset_identifier + - sender + - recipient + - value + properties: + type: + type: string + enum: + - transfer + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: object + required: + - type + - recipient + - asset_identifier + - value + properties: + type: + type: string + enum: + - mint + recipient: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + - type: object + required: + - type + - sender + - asset_identifier + - value + properties: + type: + type: string + enum: + - burn + sender: + anyOf: + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41} + title: Stacks Address + description: Stacks Address + examples: + - SP318Q55DEKHRXJK696033DQN5C54D9K2EE6DHRWP + type: string + - pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}$ + title: Smart Contract ID + description: Smart Contract ID + examples: + - SP000000000000000000002Q6VF78.pox-3 + type: string + asset_identifier: + pattern: ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$ + title: Asset Identifier + description: Asset Identifier + examples: + - SP000000000000000000002Q6VF78.pox-3::stx-token + type: string + value: + type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string + 4XX: + description: Default Response + content: + application/json: + schema: + title: Error Response + additionalProperties: true + type: object + required: + - error + properties: + error: + type: string + message: + type: string + /extended/v3/mempool/transactions: + get: + operationId: get_mempool_transactions + summary: Get mempool transactions + tags: + - Mempool + description: Retrieves a list of recently broadcasted transactions + parameters: + - schema: + minimum: 1 + default: 20 + maximum: 50 + type: integer + in: query + name: limit + required: false + description: Number of results per page + - schema: + pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ + type: string + in: query + name: cursor + required: false + description: "Cursor for paginating mempool transactions. Format: + receipt_time:tx_id" + responses: + "200": + description: Default Response + content: + application/json: + schema: + type: object + required: + - total + - limit + - cursor + - results + properties: + total: + type: integer + example: 1 + limit: + minimum: 1 + default: 20 + maximum: 50 + description: Number of results per page + type: integer + cursor: + type: object + required: + - next + - previous + - current + properties: + next: + anyOf: + - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ + description: "Cursor for paginating mempool transactions. Format: + receipt_time:tx_id" + type: string + - type: "null" + previous: + anyOf: + - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ + description: "Cursor for paginating mempool transactions. Format: + receipt_time:tx_id" + type: string + - type: "null" + current: + anyOf: + - pattern: ^\d+:(0x)?[a-fA-F0-9]{64}$ + description: "Cursor for paginating mempool transactions. Format: + receipt_time:tx_id" + type: string + - type: "null" + results: + type: array + items: + anyOf: + - title: TokenTransferMempoolTransactionSummary + description: Token transfer mempool transaction summary + type: object + required: + - tx_id + - sender + - sponsor + - fee_rate + - receipt_time + - receipt_block_height + - status + - type + - token_transfer + properties: + tx_id: + description: Transaction ID + type: string + sender: + type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + sponsor: + anyOf: + - type: object + required: + - address + - nonce + properties: + address: + description: Address of the transaction initiator + type: string + nonce: + description: Nonce of the transaction initiator + type: integer + - type: "null" + fee_rate: + description: Transaction fee as Integer string (64-bit unsigned integer). + type: string + receipt_time: + description: A unix timestamp (in seconds) indicating when the transaction + broadcast was received by the node. + type: integer + receipt_block_height: + description: Height of the block this transaction was received by the node + type: integer + status: + description: Status of the mempool transaction + anyOf: + - type: string + enum: + - pending + - type: string + enum: + - dropped_replace_by_fee + - type: string + enum: + - dropped_replace_across_fork + - type: string + enum: + - dropped_too_expensive + - type: string + enum: + - dropped_stale_garbage_collect + - type: string + enum: + - dropped_problematic + type: + type: string + enum: + - token_transfer + token_transfer: type: object required: - recipient @@ -93529,9 +98635,15 @@ paths: type: string memo: anyOf: - - description: Hex encoded arbitrary message, up to 34 bytes length (should try - decoding to an ASCII string) - type: string + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string - type: "null" - title: SmartContractMempoolTransactionSummary description: Smart contract mempool transaction summary @@ -94176,9 +99288,15 @@ paths: type: string memo: anyOf: - - description: Hex encoded arbitrary message, up to 34 bytes length (should try - decoding to an ASCII string) - type: string + - type: object + required: + - hex + - repr + properties: + hex: + type: string + repr: + type: string - type: "null" - title: SmartContractTransactionSummary description: Smart contract transaction summary diff --git a/package-lock.json b/package-lock.json index b5466b10eb..11026ea0f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,11 @@ "@fastify/type-provider-typebox": "5.2.0", "@sinclair/typebox": "0.34.48", "@stacks/api-toolkit": "1.13.0", - "@stacks/codec": "1.8.0-pox5.1", + "@stacks/codec": "2.0.0-pox5.1", "@stacks/common": "7.3.1", "@stacks/encryption": "7.4.0", "@stacks/network": "7.3.1", - "@stacks/node-publisher-client": "2.1.3", + "@stacks/node-publisher-client": "2.2.0", "@stacks/stacking": "7.4.0", "@stacks/transactions": "7.4.0", "bignumber.js": "10.0.2", @@ -73,24 +73,6 @@ "node": ">=24" } }, - "../stacks-codec-js": { - "name": "@stacks/codec", - "version": "1.6.0", - "extraneous": true, - "license": "GPL-3.0", - "dependencies": { - "@types/node": "^16.11.26", - "detect-libc": "^2.0.1" - }, - "devDependencies": { - "@stacks/prettier-config": "^0.0.10", - "@types/jest": "^27.4.1", - "cargo-cp-artifact": "^0.1.9", - "jest": "^27.5.1", - "ts-jest": "^27.1.4", - "typescript": "^4.6.3" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1717,27 +1699,18 @@ "license": "MIT" }, "node_modules/@stacks/codec": { - "version": "1.8.0-pox5.1", - "resolved": "https://registry.npmjs.org/@stacks/codec/-/codec-1.8.0-pox5.1.tgz", - "integrity": "sha512-HjGss+GqxYi0bjcd5PVo9RAutWm38rA1yORrszNxjA8QGeIp1P3eMrYYF+tPnM8CDrNpOAe+i9Mqu5goGlTMgw==", + "version": "2.0.0-pox5.1", + "resolved": "https://registry.npmjs.org/@stacks/codec/-/codec-2.0.0-pox5.1.tgz", + "integrity": "sha512-EVQ8+BR70GGy7HSaVq4Z1YQdgZods8FwFqwRgUcDAi2mO2DYUty6OUDgrWaQ+/AfTNaAzGAKqsFK0dhBIp+zaA==", "license": "GPL-3.0", "dependencies": { - "@types/node": "^20.14.0", + "@types/node": "^24.0.0", "detect-libc": "^2.0.1" }, "engines": { "node": ">=20" } }, - "node_modules/@stacks/codec/node_modules/@types/node": { - "version": "20.19.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", - "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, "node_modules/@stacks/common": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/@stacks/common/-/common-7.3.1.tgz", @@ -1788,9 +1761,9 @@ } }, "node_modules/@stacks/node-publisher-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@stacks/node-publisher-client/-/node-publisher-client-2.1.3.tgz", - "integrity": "sha512-aq4HjqHtPkDhDYM8IeVnnUuUhals7DQDSSBCuro1zaPixN2xHbatBcvtPV8evJukCnM2zt5vHpDHF8CG2/O8Kw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@stacks/node-publisher-client/-/node-publisher-client-2.2.0.tgz", + "integrity": "sha512-/O/udLosZGQRyfjrYQ9mIymYDZSQncKlheCpbNsFG8oAoZrNOR8TjiFzLhaRGci4d1FmWPvQU6H9xRrkXBbwsQ==", "license": "GPL-3.0-only", "dependencies": { "@stacks/api-toolkit": "^1.13.0", @@ -7232,12 +7205,6 @@ "node": ">=18.17" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 76a2c6e663..f33a51141c 100644 --- a/package.json +++ b/package.json @@ -66,11 +66,11 @@ "@fastify/type-provider-typebox": "5.2.0", "@sinclair/typebox": "0.34.48", "@stacks/api-toolkit": "1.13.0", - "@stacks/codec": "1.8.0-pox5.1", + "@stacks/codec": "2.0.0-pox5.1", "@stacks/common": "7.3.1", "@stacks/encryption": "7.4.0", "@stacks/network": "7.3.1", - "@stacks/node-publisher-client": "2.1.3", + "@stacks/node-publisher-client": "2.2.0", "@stacks/stacking": "7.4.0", "@stacks/transactions": "7.4.0", "bignumber.js": "10.0.2", diff --git a/src/api/controllers/db-controller.ts b/src/api/controllers/db-controller.ts index 5c8e57d90f..675839e5fd 100644 --- a/src/api/controllers/db-controller.ts +++ b/src/api/controllers/db-controller.ts @@ -9,6 +9,7 @@ import { decodeClarityValueToRepr, decodeClarityValueToTypeName, decodePostConditions, + Pox4EventName, } from '@stacks/codec'; import { BlockIdentifier, @@ -23,7 +24,7 @@ import { DbTxTypeId, DbSearchResultWithMetadata, BaseTx, - DbPoxSyntheticEvent, + DbPox4SyntheticEvent, } from '../../datastore/common.js'; import { unwrapOptional, FoundOrNot, unixEpochToIso, EMPTY_HASH_256 } from '../../helpers.js'; import { @@ -31,7 +32,6 @@ import { serializePostConditionMode, } from '../serializers/v1/post-conditions.js'; import { PgStore } from '../../datastore/pg-store.js'; -import { SyntheticPoxEventName } from '../../pox-helpers.js'; import { logger } from '@stacks/api-toolkit'; import { AbstractMempoolTransaction, @@ -225,7 +225,7 @@ export function getAssetEventTypeString( } } -export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { +export function parsePox4SyntheticEvent(poxEvent: DbPox4SyntheticEvent) { const baseInfo = { block_height: poxEvent.block_height, tx_id: poxEvent.tx_id, @@ -240,7 +240,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { name: poxEvent.name, }; switch (poxEvent.name) { - case SyntheticPoxEventName.HandleUnlock: { + case Pox4EventName.HandleUnlock: { return { ...baseInfo, data: { @@ -249,7 +249,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackStx: { + case Pox4EventName.StackStx: { return { ...baseInfo, data: { @@ -263,7 +263,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackIncrease: { + case Pox4EventName.StackIncrease: { return { ...baseInfo, data: { @@ -275,7 +275,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackExtend: { + case Pox4EventName.StackExtend: { return { ...baseInfo, data: { @@ -287,7 +287,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.DelegateStx: { + case Pox4EventName.DelegateStx: { return { ...baseInfo, data: { @@ -299,7 +299,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.DelegateStackStx: { + case Pox4EventName.DelegateStackStx: { return { ...baseInfo, data: { @@ -313,7 +313,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.DelegateStackIncrease: { + case Pox4EventName.DelegateStackIncrease: { return { ...baseInfo, data: { @@ -325,7 +325,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.DelegateStackExtend: { + case Pox4EventName.DelegateStackExtend: { return { ...baseInfo, data: { @@ -337,7 +337,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackAggregationCommit: { + case Pox4EventName.StackAggregationCommit: { return { ...baseInfo, data: { @@ -349,7 +349,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackAggregationCommitIndexed: { + case Pox4EventName.StackAggregationCommitIndexed: { return { ...baseInfo, data: { @@ -361,7 +361,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.StackAggregationIncrease: { + case Pox4EventName.StackAggregationIncrease: { return { ...baseInfo, data: { @@ -372,7 +372,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }, }; } - case SyntheticPoxEventName.RevokeDelegateStx: { + case Pox4EventName.RevokeDelegateStx: { return { ...baseInfo, data: { @@ -383,7 +383,7 @@ export function parsePoxSyntheticEvent(poxEvent: DbPoxSyntheticEvent) { }; } default: - throw new Error(`Unexpected Pox2 event name ${(poxEvent as DbPoxSyntheticEvent).name}`); + throw new Error(`Unexpected Pox4 event name ${(poxEvent as DbPox4SyntheticEvent).name}`); } } diff --git a/src/api/routes/v1/pox.ts b/src/api/routes/v1/pox.ts index 738d374f90..7d52f43953 100644 --- a/src/api/routes/v1/pox.ts +++ b/src/api/routes/v1/pox.ts @@ -1,5 +1,5 @@ import { getPagingQueryLimit, parsePagingQueryInput, ResourceType } from '../../pagination.js'; -import { parsePoxSyntheticEvent } from '../../controllers/db-controller.js'; +import { parsePox4SyntheticEvent } from '../../controllers/db-controller.js'; import { getBlockParams, validatePrincipal, validateRequestHexInput } from '../../query-helpers.js'; import { handleChainTipCache } from '../../controllers/cache-controller.js'; import { FastifyPluginAsync } from 'fastify'; @@ -69,7 +69,7 @@ export const PoxRoutes: FastifyPluginAsync< limit, poxTable, }); - const parsedResult = queryResults.map(r => parsePoxSyntheticEvent(r)); + const parsedResult = queryResults.map(r => parsePox4SyntheticEvent(r)); const response = { limit, offset, @@ -105,7 +105,7 @@ export const PoxRoutes: FastifyPluginAsync< if (!queryResults.found) { throw new NotFoundError(`could not find transaction by ID`); } - const parsedResult = queryResults.result.map(r => parsePoxSyntheticEvent(r)); + const parsedResult = queryResults.result.map(r => parsePox4SyntheticEvent(r)); const response = { results: parsedResult, }; @@ -139,7 +139,7 @@ export const PoxRoutes: FastifyPluginAsync< if (!queryResults.found) { throw new NotFoundError(`could not find principal`); } - const parsedResult = queryResults.result.map(r => parsePoxSyntheticEvent(r)); + const parsedResult = queryResults.result.map(r => parsePox4SyntheticEvent(r)); const response = { results: parsedResult, }; diff --git a/src/api/routes/v1/status.ts b/src/api/routes/v1/status.ts index 0fa75d1d48..e991a050d3 100644 --- a/src/api/routes/v1/status.ts +++ b/src/api/routes/v1/status.ts @@ -31,25 +31,17 @@ export const StatusRoutes: FastifyPluginAsync< status: 'ready', }; try { - await fastify.db.sqlTransaction(async sql => { - const poxForceUnlockHeights = await fastify.db.getPoxForcedUnlockHeightsInternal(sql); - if (poxForceUnlockHeights.found) { - response.pox_v1_unlock_height = poxForceUnlockHeights.result.pox1UnlockHeight as number; - response.pox_v2_unlock_height = poxForceUnlockHeights.result.pox2UnlockHeight as number; - response.pox_v3_unlock_height = poxForceUnlockHeights.result.pox3UnlockHeight as number; - } - const chainTip = await fastify.db.getChainTip(sql); - if (chainTip.block_height > 0) { - response.chain_tip = { - block_height: chainTip.block_height, - block_hash: chainTip.block_hash, - index_block_hash: chainTip.index_block_hash, - microblock_hash: chainTip.microblock_hash, - microblock_sequence: chainTip.microblock_sequence, - burn_block_height: chainTip.burn_block_height, - }; - } - }); + const chainTip = await fastify.db.getChainTip(fastify.db.sql); + if (chainTip.block_height > 0) { + response.chain_tip = { + block_height: chainTip.block_height, + block_hash: chainTip.block_hash, + index_block_hash: chainTip.index_block_hash, + microblock_hash: chainTip.microblock_hash, + microblock_sequence: chainTip.microblock_sequence, + burn_block_height: chainTip.burn_block_height, + }; + } } catch (_error) { // ignore error } diff --git a/src/api/routes/v3/principals.ts b/src/api/routes/v3/principals.ts index 5e725b0298..8cc4125331 100644 --- a/src/api/routes/v3/principals.ts +++ b/src/api/routes/v3/principals.ts @@ -56,5 +56,20 @@ export const PrincipalsRoutes: FastifyPluginAsync< } ); + fastify.get( + '/principals/:principal/balances/staking', + { + schema: { + operationId: 'get_principal_staking_balances', + summary: 'Get principal staking balances', + description: 'Get principal staking balances', + tags: ['Staking'], + }, + }, + async (_req, reply) => { + await reply.send(); + } + ); + await Promise.resolve(); }; diff --git a/src/api/routes/v3/staking-bonds.ts b/src/api/routes/v3/staking-bonds.ts new file mode 100644 index 0000000000..a6576091fd --- /dev/null +++ b/src/api/routes/v3/staking-bonds.ts @@ -0,0 +1,231 @@ +import { FastifyPluginAsync } from 'fastify'; +import { Server } from 'node:http'; +import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'; +import { handleChainTipCache } from '../../controllers/cache-controller.js'; +import { getPagingQueryLimit, ResourceType } from '../../pagination.js'; +import { + BondCursorSchema, + CursorPaginatedResponse, + CursorPaginationQuerystring, + TransactionCursorSchema, +} from '../../schemas/v3/cursors.js'; +import { BondSchema, BondSummarySchema } from '../../schemas/v3/entities/bonds.js'; +import { BondAllowlistSchema } from '../../schemas/v3/entities/bond-allowlist-entries.js'; +import { BondRegistrationSchema } from '../../schemas/v3/entities/bond-registrations.js'; +import { BondIndexSchema, PrincipalSchema } from '../../schemas/v3/entities/common.js'; +import { + serializeDbBond, + serializeDbBondAllowlistEntry, + serializeDbBondRegistration, + serializeDbBondSummary, +} from '../../serializers/v3/bonds.js'; +import { NotFoundError } from '../../../errors.js'; + +export const StakingBondsRoutes: FastifyPluginAsync< + Record, + Server, + TypeBoxTypeProvider +> = async fastify => { + fastify.get( + '/staking/bonds', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bonds', + summary: 'Get bonds', + description: 'Get bonds', + tags: ['Staking'], + querystring: CursorPaginationQuerystring(BondCursorSchema, ResourceType.Tx), + response: { + 200: CursorPaginatedResponse(BondSummarySchema, BondCursorSchema, ResourceType.Tx), + }, + }, + }, + async (req, reply) => { + const results = await fastify.db.v3.getBondSummaries({ + limit: req.query.limit ?? getPagingQueryLimit(ResourceType.Tx), + cursor: req.query.cursor, + }); + await reply.send({ + limit: results.limit, + total: results.total, + cursor: { + next: results.next_cursor, + previous: results.prev_cursor, + current: results.current_cursor, + }, + results: results.results.map(r => serializeDbBondSummary(r, results.burn_block_height)), + }); + } + ); + + fastify.get( + '/staking/bonds/:bond_index', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bond', + summary: 'Get bond', + description: 'Get bond', + tags: ['Staking'], + params: Type.Object({ + bond_index: BondIndexSchema, + }), + response: { + 200: BondSchema, + }, + }, + }, + async (req, reply) => { + const bond = await fastify.db.v3.getBond({ bondIndex: req.params.bond_index }); + if (!bond) { + throw new NotFoundError('Bond not found'); + } + await reply.send(serializeDbBond(bond, bond.burn_block_height)); + } + ); + + fastify.get( + '/staking/bonds/:bond_index/allowlist', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bond_allowlist_entries', + summary: 'Get bond allowlist entries', + description: 'Get bond allowlist entries', + tags: ['Staking'], + params: Type.Object({ + bond_index: BondIndexSchema, + }), + querystring: CursorPaginationQuerystring(TransactionCursorSchema, ResourceType.Tx), + response: { + 200: CursorPaginatedResponse( + BondAllowlistSchema, + TransactionCursorSchema, + ResourceType.Tx + ), + }, + }, + }, + async (req, reply) => { + const results = await fastify.db.v3.getBondAllowlistEntries({ + bondIndex: req.params.bond_index, + limit: req.query.limit ?? getPagingQueryLimit(ResourceType.Tx), + cursor: req.query.cursor, + }); + await reply.send({ + limit: results.limit, + total: results.total, + cursor: { + next: results.next_cursor, + previous: results.prev_cursor, + current: results.current_cursor, + }, + results: results.results.map(r => serializeDbBondAllowlistEntry(r)), + }); + } + ); + + fastify.get( + '/staking/bonds/:bond_index/allowlist/:principal', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bond_allowlist_entry', + summary: 'Get bond allowlist entry', + description: 'Get bond allowlist entry', + tags: ['Staking'], + params: Type.Object({ + bond_index: BondIndexSchema, + principal: PrincipalSchema, + }), + response: { + 200: BondAllowlistSchema, + }, + }, + }, + async (req, reply) => { + const entry = await fastify.db.v3.getBondAllowlistEntry({ + bondIndex: req.params.bond_index, + principal: req.params.principal, + }); + if (!entry) { + throw new NotFoundError('Bond allowlist entry not found'); + } + await reply.send(serializeDbBondAllowlistEntry(entry)); + } + ); + + fastify.get( + '/staking/bonds/:bond_index/registrations', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bond_registrations', + summary: 'Get bond registrations', + description: 'Get bond registrations', + tags: ['Staking'], + params: Type.Object({ + bond_index: BondIndexSchema, + }), + querystring: CursorPaginationQuerystring(TransactionCursorSchema, ResourceType.Tx), + response: { + 200: CursorPaginatedResponse( + BondRegistrationSchema, + TransactionCursorSchema, + ResourceType.Tx + ), + }, + }, + }, + async (req, reply) => { + const results = await fastify.db.v3.getBondRegistrations({ + bondIndex: req.params.bond_index, + limit: req.query.limit ?? getPagingQueryLimit(ResourceType.Tx), + cursor: req.query.cursor, + }); + await reply.send({ + limit: results.limit, + total: results.total, + cursor: { + next: results.next_cursor, + previous: results.prev_cursor, + current: results.current_cursor, + }, + results: results.results.map(r => serializeDbBondRegistration(r)), + }); + } + ); + + fastify.get( + '/staking/bonds/:bond_index/registrations/:principal', + { + preHandler: handleChainTipCache, + schema: { + operationId: 'get_bond_registration', + summary: 'Get bond registration', + description: 'Get bond registration', + tags: ['Staking'], + params: Type.Object({ + bond_index: BondIndexSchema, + principal: PrincipalSchema, + }), + response: { + 200: BondRegistrationSchema, + }, + }, + }, + async (req, reply) => { + const registration = await fastify.db.v3.getBondRegistration({ + bondIndex: req.params.bond_index, + principal: req.params.principal, + }); + if (!registration) { + throw new NotFoundError('Bond registration not found'); + } + await reply.send(serializeDbBondRegistration(registration)); + } + ); + + await Promise.resolve(); +}; diff --git a/src/api/routes/v3/staking-principals.ts b/src/api/routes/v3/staking-principals.ts new file mode 100644 index 0000000000..7917074006 --- /dev/null +++ b/src/api/routes/v3/staking-principals.ts @@ -0,0 +1,30 @@ +import { FastifyPluginAsync } from 'fastify'; +import { Server } from 'node:http'; +import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'; +import { PrincipalSchema } from '../../schemas/v3/entities/common.js'; + +export const StakingPrincipalsRoutes: FastifyPluginAsync< + Record, + Server, + TypeBoxTypeProvider +> = async fastify => { + fastify.get( + '/staking/principals/:principal/positions', + { + schema: { + operationId: 'get_principal_staking_positions', + summary: 'Get principal staking positions', + description: 'Get principal staking positions', + tags: ['Staking'], + params: Type.Object({ + principal: PrincipalSchema, + }), + }, + }, + async (_req, reply) => { + await reply.send(); + } + ); + + await Promise.resolve(); +}; diff --git a/src/api/routes/v3/transactions.ts b/src/api/routes/v3/transactions.ts index 43521f2bbe..2a8903913b 100644 --- a/src/api/routes/v3/transactions.ts +++ b/src/api/routes/v3/transactions.ts @@ -12,6 +12,7 @@ import { CursorPaginatedResponse, CursorPaginationQuerystring, TransactionCursorSchema, + TransactionEventCursorSchema, } from '../../schemas/v3/cursors.js'; import { TransactionIdSchema } from '../../schemas/v3/entities/common.js'; import { @@ -20,6 +21,8 @@ import { } from '../../schemas/v3/entities/transactions.js'; import { MempoolTransactionSchema } from '../../schemas/v3/entities/mempool-transactions.js'; import { NotFoundError } from '../../../errors.js'; +import { TransactionEventSchema } from '../../schemas/v3/entities/transaction-events.js'; +import { serializeDbTransactionEvent } from '../../serializers/v3/transaction-events.js'; export const TransactionsRoutes: FastifyPluginAsync< Record, @@ -116,5 +119,46 @@ export const TransactionsRoutes: FastifyPluginAsync< } ); + fastify.get( + '/transactions/:tx_id/events', + { + preHandler: handleTransactionCache, + schema: { + operationId: 'get_transaction_events', + summary: 'Get transaction events', + description: `Retrieves events for a given transaction ID`, + tags: ['Transactions'], + params: Type.Object({ + tx_id: TransactionIdSchema, + }), + querystring: CursorPaginationQuerystring(TransactionEventCursorSchema, ResourceType.Event), + response: { + 200: CursorPaginatedResponse( + TransactionEventSchema, + TransactionEventCursorSchema, + ResourceType.Event + ), + }, + }, + }, + async (req, reply) => { + const events = await fastify.db.v3.getTransactionEvents({ + txId: req.params.tx_id, + limit: getPagingQueryLimit(ResourceType.Event, req.query.limit), + cursor: req.query.cursor, + }); + await reply.send({ + total: events.total, + limit: events.limit, + cursor: { + next: events.next_cursor, + previous: events.prev_cursor, + current: events.current_cursor, + }, + results: events.results.map(r => serializeDbTransactionEvent(r)), + }); + } + ); + await Promise.resolve(); }; diff --git a/src/api/schemas/v1/responses/responses.ts b/src/api/schemas/v1/responses/responses.ts index a4060ccb48..f806fc3576 100644 --- a/src/api/schemas/v1/responses/responses.ts +++ b/src/api/schemas/v1/responses/responses.ts @@ -33,9 +33,6 @@ export const ServerStatusResponseSchema = Type.Object( status: Type.String({ description: 'the current server status', }), - pox_v1_unlock_height: OptionalNullable(Type.Integer()), - pox_v2_unlock_height: OptionalNullable(Type.Integer()), - pox_v3_unlock_height: OptionalNullable(Type.Integer()), chain_tip: OptionalNullable( Type.Object({ block_height: Type.Integer({ diff --git a/src/api/schemas/v3/cursors.ts b/src/api/schemas/v3/cursors.ts index 7514e7e910..61d2865d40 100644 --- a/src/api/schemas/v3/cursors.ts +++ b/src/api/schemas/v3/cursors.ts @@ -1,4 +1,4 @@ -import { ObjectOptions, TSchema, Type } from '@sinclair/typebox'; +import { ObjectOptions, Static, TSchema, Type } from '@sinclair/typebox'; import { pagingQueryLimits, ResourceType } from '../../pagination.js'; import { Nullable } from '../v1/util.js'; @@ -58,8 +58,22 @@ export const TransactionCursorSchema = Type.String({ 'Cursor for paginating transactions. Format: block_height:microblock_sequence:tx_index', pattern: '^[0-9]+:[0-9]+:[0-9]+$', }); +export type TransactionCursor = Static; export const MempoolTransactionCursorSchema = Type.String({ pattern: '^\\d+:(0x)?[a-fA-F0-9]{64}$', description: 'Cursor for paginating mempool transactions. Format: receipt_time:tx_id', }); +export type MempoolTransactionCursor = Static; + +export const TransactionEventCursorSchema = Type.String({ + pattern: '^[0-9]+$', + description: 'Cursor for paginating transaction events. Format: event_index', +}); +export type TransactionEventCursor = Static; + +export const BondCursorSchema = Type.String({ + pattern: '^\\d+$', + description: 'Cursor for paginating bonds. Format: bond_index', +}); +export type BondCursor = Static; diff --git a/src/api/schemas/v3/entities/bond-allowlist-entries.ts b/src/api/schemas/v3/entities/bond-allowlist-entries.ts new file mode 100644 index 0000000000..5f62d0cc26 --- /dev/null +++ b/src/api/schemas/v3/entities/bond-allowlist-entries.ts @@ -0,0 +1,7 @@ +import { Static, Type } from '@sinclair/typebox'; + +export const BondAllowlistSchema = Type.Object({ + staker: Type.String(), + max_sats: Type.String(), +}); +export type BondAllowlist = Static; diff --git a/src/api/schemas/v3/entities/bond-registrations.ts b/src/api/schemas/v3/entities/bond-registrations.ts new file mode 100644 index 0000000000..b54b322332 --- /dev/null +++ b/src/api/schemas/v3/entities/bond-registrations.ts @@ -0,0 +1,14 @@ +import { Static, Type } from '@sinclair/typebox'; + +export const BondRegistrationSchema = Type.Object({ + bond_index: Type.Integer(), + signer: Type.String(), + staker: Type.String(), + amount_ustx: Type.String(), + sats_total: Type.String(), + first_reward_cycle: Type.Integer(), + unlock_burn_height: Type.Integer(), + unlock_cycle: Type.Integer(), + is_l1_lock: Type.Boolean(), +}); +export type BondRegistration = Static; diff --git a/src/api/schemas/v3/entities/bonds.ts b/src/api/schemas/v3/entities/bonds.ts new file mode 100644 index 0000000000..307ca969c5 --- /dev/null +++ b/src/api/schemas/v3/entities/bonds.ts @@ -0,0 +1,86 @@ +import { Static, Type } from '@sinclair/typebox'; +import { BitcoinBlockPositionSchema, BlockPositionSchema, BondIndexSchema } from './common.js'; + +export const BondStatusSchema = Type.Union([ + Type.Literal('upcoming'), + Type.Literal('active'), + Type.Literal('unlocked'), +]); +export type BondStatus = Static; + +export const BondBalancesSchema = Type.Object({ + locked: Type.Object({ + btc: Type.String({ + description: 'The total amount of BTC that is locked up for this bond', + }), + stx: Type.String({ + description: 'The total amount of STX that is locked up for this bond', + }), + }), + paid_out: Type.Object({ + btc: Type.String({ + description: 'The total amount of BTC that has been paid out for this bond', + }), + }), +}); +export type BondBalances = Static; + +export const BondSummarySchema = Type.Object({ + index: BondIndexSchema, + pox_version: Type.Literal('pox5'), + status: BondStatusSchema, + parameters: Type.Object({ + target_rate_bps: Type.Integer({ description: 'The target yield rate (APY) in basis points' }), + stx_value_ratio: Type.Integer({ + description: + 'This is a representation of the STXBTC price. The value represents "uSTX per 100 sats"', + }), + minimum_stx_ratio: Type.Integer({ + description: + 'The amount of STX that must be locked relative to BTC, in equal-valued terms (ie in USD terms). This value is represented in basis points.', + }), + btc_capacity: Type.String({ + description: 'The total capacity of BTC that can be locked up for this bond', + }), + }), + registrations: Type.Object({ + allowed_count: Type.Integer({ + description: 'The number of entries in the allowlist for this bond', + }), + registered_count: Type.Integer({ + description: 'The number of registrations for this bond', + }), + }), + schedule: Type.Object({ + activation: Type.Object({ + bitcoin_height: Type.Integer({ + description: 'The height at which the bond was activated', + }), + pox_cycle: Type.Integer({ + description: 'The POX cycle at which the bond was activated', + }), + }), + unlock: Type.Object({ + bitcoin_height: Type.Integer({ + description: 'The height at which the bond can be unlocked', + }), + pox_cycle: Type.Integer({ + description: 'The POX cycle at which the bond can be unlocked', + }), + }), + }), + balances: BondBalancesSchema, +}); +export type BondSummary = Static; + +export const BondSchema = Type.Composite([ + BondSummarySchema, + Type.Object({ + transaction: Type.Object({ + tx_id: Type.String({ description: 'The transaction ID that created the bond' }), + block: BlockPositionSchema, + bitcoin_block: BitcoinBlockPositionSchema, + }), + }), +]); +export type Bond = Static; diff --git a/src/api/schemas/v3/entities/common.ts b/src/api/schemas/v3/entities/common.ts index 3690e6bf3c..569a519289 100644 --- a/src/api/schemas/v3/entities/common.ts +++ b/src/api/schemas/v3/entities/common.ts @@ -19,6 +19,15 @@ export type SmartContractId = Static; export const PrincipalSchema = Type.Union([AddressSchema, SmartContractIdSchema]); export type Principal = Static; +export const AssetIdentifierSchema = Type.String({ + pattern: + '^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}\\.[a-zA-Z]([a-zA-Z0-9]|[-_]){0,39}::[a-zA-Z-]+$', + title: 'Asset Identifier', + description: 'Asset Identifier', + examples: ['SP000000000000000000002Q6VF78.pox-3::stx-token'], +}); +export type AssetIdentifier = Static; + export const TransactionIdSchema = Type.String({ pattern: '^(0x)?[a-fA-F0-9]{64}$', title: 'Transaction ID', @@ -51,12 +60,23 @@ export const BlockHeightOrHashSchema = Type.Union([ ]); export type BlockHeightOrHash = Static; +export const BondIndexSchema = Type.Integer({ + description: 'The index of the bond in the PoX-5 bond list', +}); +export type BondIndex = Static; + export const DecodedClarityValueSchema = Type.Object({ hex: Type.String(), repr: Type.String(), }); export type DecodedClarityValue = Static; +export const DecodedStxTransferMemoSchema = Type.Object({ + hex: Type.String(), + repr: Type.String(), +}); +export type DecodedStxTransferMemo = Static; + export const ExecutionCostSchema = Type.Object({ read_count: Type.Integer({ description: 'Number of reads in the transaction', @@ -75,3 +95,41 @@ export const ExecutionCostSchema = Type.Object({ }), }); export type ExecutionCost = Static; + +export const BlockPositionSchema = Type.Object({ + height: Type.Integer({ + description: 'Height of the block this transactions was associated with', + }), + hash: Type.String({ + description: 'Hash of the blocked this transactions was associated with', + }), + index_hash: Type.String({ + description: 'Hash of the index block this transactions was associated with', + }), + time: Type.Number({ + description: 'Unix timestamp (in seconds) indicating when this block was mined.', + }), + tx_index: Type.Integer({ + description: + 'Index of the transaction, indicating the order. Starts at `0` and increases with each transaction', + }), +}); +export type BlockPosition = Static; + +export const BitcoinBlockPositionSchema = Type.Object({ + height: Type.Integer({ + description: 'Height of the anchor burn block.', + }), + time: Type.Number({ + description: 'Unix timestamp (in seconds) indicating when this block was mined.', + }), +}); +export type BitcoinBlockPosition = Static; + +export const AmountSchema = Type.String({ + pattern: '^[0-9]+$', + title: 'Amount', + description: 'Amount', + examples: ['1000000'], +}); +export type Amount = Static; diff --git a/src/api/schemas/v3/entities/mempool-transaction-summaries.ts b/src/api/schemas/v3/entities/mempool-transaction-summaries.ts index 11cbe11fc5..726c80cda5 100644 --- a/src/api/schemas/v3/entities/mempool-transaction-summaries.ts +++ b/src/api/schemas/v3/entities/mempool-transaction-summaries.ts @@ -1,6 +1,7 @@ import { Static, Type } from '@sinclair/typebox'; import { TransactionSenderSchema } from './transaction-summaries.js'; import { Nullable } from '../../v1/util.js'; +import { DecodedStxTransferMemoSchema } from './common.js'; const MempoolTransactionStatusSchema = Type.Union( [ @@ -45,12 +46,7 @@ export const TokenTransferMempoolTransactionSummarySchema = Type.Composite( amount: Type.String({ description: 'Transfer amount as Integer string (64-bit unsigned integer)', }), - memo: Nullable( - Type.String({ - description: - 'Hex encoded arbitrary message, up to 34 bytes length (should try decoding to an ASCII string)', - }) - ), + memo: Nullable(DecodedStxTransferMemoSchema), }), }), ], diff --git a/src/api/schemas/v3/entities/mempool-transactions.ts b/src/api/schemas/v3/entities/mempool-transactions.ts index 8ed6e9ded5..a2497b27b3 100644 --- a/src/api/schemas/v3/entities/mempool-transactions.ts +++ b/src/api/schemas/v3/entities/mempool-transactions.ts @@ -2,7 +2,7 @@ import { Static, Type } from '@sinclair/typebox'; import { BaseMempoolTransactionSummarySchema } from './mempool-transaction-summaries.js'; import { PostConditionSchema } from './post-conditions.js'; import { Nullable } from '../../v1/util.js'; -import { DecodedClarityValueSchema } from './common.js'; +import { DecodedClarityValueSchema, DecodedStxTransferMemoSchema } from './common.js'; const BaseMempoolTransactionSchema = Type.Composite([ BaseMempoolTransactionSummarySchema, @@ -32,11 +32,7 @@ const TokenTransferMempoolTransactionSchema = Type.Composite([ amount: Type.String({ description: 'Amount of the token transfer', }), - memo: Nullable( - Type.String({ - description: 'Memo of the token transfer', - }) - ), + memo: Nullable(DecodedStxTransferMemoSchema), }), }), ]); diff --git a/src/api/schemas/v3/entities/principal-bond-positions.ts b/src/api/schemas/v3/entities/principal-bond-positions.ts new file mode 100644 index 0000000000..efa37ab811 --- /dev/null +++ b/src/api/schemas/v3/entities/principal-bond-positions.ts @@ -0,0 +1,35 @@ +import { Static, Type } from '@sinclair/typebox'; +import { BondBalancesSchema } from './bonds.js'; +import { BondIndexSchema, TransactionIdSchema } from './common.js'; + +export const PrincipalBondPositionStatusSchema = Type.Union([ + Type.Literal('enrolled'), + Type.Literal('running'), + Type.Literal('early_exit'), + Type.Literal('unlocked'), +]); +export type PrincipalBondPositionStatus = Static; + +export const PrincipalBondPositionSchema = Type.Object({ + bond_index: BondIndexSchema, + status: PrincipalBondPositionStatusSchema, + active: Type.Boolean({ + description: 'Whether the position is active', + }), + balances: BondBalancesSchema, + enrollment: Type.Object({ + tx_id: TransactionIdSchema, + pox_address: Type.String({ + description: 'The POX address of the principal', + }), + btc_lockup: Type.Object({ + amount: Type.String({ + description: 'The amount of BTC that is locked up for this principal', + }), + }), + }), + amount: Type.String({ + description: 'The amount of STX that is locked up for this principal', + }), +}); +export type PrincipalBondPosition = Static; diff --git a/src/api/schemas/v3/entities/transaction-events.ts b/src/api/schemas/v3/entities/transaction-events.ts new file mode 100644 index 0000000000..9f14e72d38 --- /dev/null +++ b/src/api/schemas/v3/entities/transaction-events.ts @@ -0,0 +1,134 @@ +import { Static, Type } from '@sinclair/typebox'; +import { + AssetIdentifierSchema, + DecodedClarityValueSchema, + PrincipalSchema, + SmartContractIdSchema, + BlockHeightSchema, + AmountSchema, +} from './common.js'; +import { Nullable } from '../../v1/util.js'; + +const BaseTransactionEventSchema = Type.Object({ + event_index: Type.Integer(), +}); + +export const ContractLogTransactionEventSchema = Type.Composite([ + BaseTransactionEventSchema, + Type.Object({ + type: Type.Literal('contract_log'), + contract_log: Type.Object({ + contract_id: SmartContractIdSchema, + topic: Type.Literal('print'), + value: DecodedClarityValueSchema, + }), + }), +]); +export type ContractLogTransactionEvent = Static; + +const StxLockTransactionEventSchema = Type.Composite([ + BaseTransactionEventSchema, + Type.Object({ + type: Type.Literal('stx_lock'), + stx_lock: Type.Object({ + amount: AmountSchema, + unlock_bitcoin_height: BlockHeightSchema, + address: PrincipalSchema, + }), + }), +]); +export type StxLockTransactionEvent = Static; + +const StxTransactionEventSchema = Type.Composite([ + BaseTransactionEventSchema, + Type.Object({ + type: Type.Literal('stx_asset'), + stx_asset: Type.Union([ + Type.Object({ + type: Type.Literal('transfer'), + sender: PrincipalSchema, + recipient: PrincipalSchema, + amount: AmountSchema, + memo: Nullable(DecodedClarityValueSchema), + }), + Type.Object({ + type: Type.Literal('mint'), + recipient: PrincipalSchema, + amount: AmountSchema, + }), + Type.Object({ + type: Type.Literal('burn'), + sender: PrincipalSchema, + amount: AmountSchema, + }), + ]), + }), +]); +export type StxTransactionEvent = Static; + +export const FtTransactionEventSchema = Type.Composite([ + BaseTransactionEventSchema, + Type.Object({ + type: Type.Literal('ft_asset'), + ft_asset: Type.Union([ + Type.Object({ + type: Type.Literal('transfer'), + asset_identifier: AssetIdentifierSchema, + sender: PrincipalSchema, + recipient: PrincipalSchema, + amount: AmountSchema, + }), + Type.Object({ + type: Type.Literal('mint'), + recipient: PrincipalSchema, + asset_identifier: AssetIdentifierSchema, + amount: AmountSchema, + }), + Type.Object({ + type: Type.Literal('burn'), + sender: PrincipalSchema, + asset_identifier: AssetIdentifierSchema, + amount: AmountSchema, + }), + ]), + }), +]); +export type FtTransactionEvent = Static; + +export const NftTransactionEventSchema = Type.Composite([ + BaseTransactionEventSchema, + Type.Object({ + type: Type.Literal('nft_asset'), + nft_asset: Type.Union([ + Type.Object({ + type: Type.Literal('transfer'), + asset_identifier: AssetIdentifierSchema, + sender: PrincipalSchema, + recipient: PrincipalSchema, + value: DecodedClarityValueSchema, + }), + Type.Object({ + type: Type.Literal('mint'), + recipient: PrincipalSchema, + asset_identifier: AssetIdentifierSchema, + value: DecodedClarityValueSchema, + }), + Type.Object({ + type: Type.Literal('burn'), + sender: PrincipalSchema, + asset_identifier: AssetIdentifierSchema, + value: DecodedClarityValueSchema, + }), + ]), + }), +]); +export type NftTransactionEvent = Static; + +export const TransactionEventSchema = Type.Union([ + ContractLogTransactionEventSchema, + StxLockTransactionEventSchema, + StxTransactionEventSchema, + FtTransactionEventSchema, + NftTransactionEventSchema, +]); +export type TransactionEvent = Static; diff --git a/src/api/schemas/v3/entities/transaction-summaries.ts b/src/api/schemas/v3/entities/transaction-summaries.ts index 23a2e74601..808de5b355 100644 --- a/src/api/schemas/v3/entities/transaction-summaries.ts +++ b/src/api/schemas/v3/entities/transaction-summaries.ts @@ -1,5 +1,10 @@ import { Static, Type } from '@sinclair/typebox'; import { Nullable } from '../../v1/util.js'; +import { + BitcoinBlockPositionSchema, + BlockPositionSchema, + DecodedStxTransferMemoSchema, +} from './common.js'; export const TransactionSenderSchema = Type.Object({ address: Type.String({ @@ -47,32 +52,8 @@ export const BaseTransactionSummarySchema = Type.Object({ fee_rate: Type.String({ description: 'Transaction fee as Integer string (64-bit unsigned integer).', }), - block: Type.Object({ - height: Type.Integer({ - description: 'Height of the block this transactions was associated with', - }), - hash: Type.String({ - description: 'Hash of the blocked this transactions was associated with', - }), - index_hash: Type.String({ - description: 'Hash of the index block this transactions was associated with', - }), - time: Type.Number({ - description: 'Unix timestamp (in seconds) indicating when this block was mined.', - }), - tx_index: Type.Integer({ - description: - 'Index of the transaction, indicating the order. Starts at `0` and increases with each transaction', - }), - }), - bitcoin_block: Type.Object({ - height: Type.Integer({ - description: 'Height of the anchor burn block.', - }), - time: Type.Number({ - description: 'Unix timestamp (in seconds) indicating when this block was mined.', - }), - }), + block: BlockPositionSchema, + bitcoin_block: BitcoinBlockPositionSchema, status: TransactionStatusSchema, }); export type BaseTransactionSummary = Static; @@ -87,12 +68,7 @@ export const TokenTransferTransactionSummarySchema = Type.Composite( amount: Type.String({ description: 'Transfer amount as Integer string (64-bit unsigned integer)', }), - memo: Nullable( - Type.String({ - description: - 'Hex encoded arbitrary message, up to 34 bytes length (should try decoding to an ASCII string)', - }) - ), + memo: Nullable(DecodedStxTransferMemoSchema), }), }), ], diff --git a/src/api/schemas/v3/entities/transactions.ts b/src/api/schemas/v3/entities/transactions.ts index 71f3603ead..26bf4797b8 100644 --- a/src/api/schemas/v3/entities/transactions.ts +++ b/src/api/schemas/v3/entities/transactions.ts @@ -2,7 +2,11 @@ import { Static, Type } from '@sinclair/typebox'; import { BaseTransactionSummarySchema, TenureChangeCauseSchema } from './transaction-summaries.js'; import { PostConditionSchema } from './post-conditions.js'; import { Nullable } from '../../v1/util.js'; -import { DecodedClarityValueSchema, ExecutionCostSchema } from './common.js'; +import { + DecodedClarityValueSchema, + DecodedStxTransferMemoSchema, + ExecutionCostSchema, +} from './common.js'; const BaseTransactionSchema = Type.Composite([ BaseTransactionSummarySchema, @@ -45,11 +49,7 @@ const TokenTransferTransactionSchema = Type.Composite([ amount: Type.String({ description: 'Amount of the token transfer', }), - memo: Nullable( - Type.String({ - description: 'Memo of the token transfer', - }) - ), + memo: Nullable(DecodedStxTransferMemoSchema), }), }), ]); diff --git a/src/api/schemas/v3/responses/principal-staking-balances-response.ts b/src/api/schemas/v3/responses/principal-staking-balances-response.ts new file mode 100644 index 0000000000..ce1deb10d2 --- /dev/null +++ b/src/api/schemas/v3/responses/principal-staking-balances-response.ts @@ -0,0 +1,13 @@ +import { Static, Type } from '@sinclair/typebox'; + +export const PrincipalStakingBalancesResponseSchema = Type.Object({ + bonds: Type.String({ + description: 'The total amount of STX that is locked up for this principal', + }), + stx: Type.String({ + description: 'The total amount of STX that is locked up for this principal', + }), +}); +export type PrincipalStakingBalancesResponse = Static< + typeof PrincipalStakingBalancesResponseSchema +>; diff --git a/src/api/serializers/v3/bonds.ts b/src/api/serializers/v3/bonds.ts new file mode 100644 index 0000000000..548a89b715 --- /dev/null +++ b/src/api/serializers/v3/bonds.ts @@ -0,0 +1,111 @@ +import { + DbBond, + DbBondAllowlistEntry, + DbBondRegistration, + DbBondSummary, +} from '../../../datastore/v3/types.js'; +import { Bond, BondSummary } from '../../schemas/v3/entities/bonds.js'; +import { BondStatus } from '../../schemas/v3/entities/bonds.js'; +import { BondAllowlist } from '../../schemas/v3/entities/bond-allowlist-entries.js'; +import { BondRegistration } from '../../schemas/v3/entities/bond-registrations.js'; + +function getBondStatus(summary: DbBondSummary, currentBurnBlockHeight: number): BondStatus { + if (currentBurnBlockHeight < summary.bond_start_height) { + return 'upcoming'; + } + if (currentBurnBlockHeight < summary.unlock_burn_height) { + return 'active'; + } + return 'unlocked'; +} + +/** + * Serializes a database bond summary to a API bond summary. + * @param summary - The database bond summary to serialize. + * @returns The API bond summary. + */ +export function serializeDbBondSummary( + summary: DbBondSummary, + currentBurnBlockHeight: number +): BondSummary { + return { + index: summary.bond_index, + pox_version: 'pox5', + status: getBondStatus(summary, currentBurnBlockHeight), + parameters: { + target_rate_bps: summary.target_rate, + stx_value_ratio: summary.stx_value_ratio, + minimum_stx_ratio: summary.min_ustx_ratio, + btc_capacity: summary.btc_capacity, + }, + registrations: { + allowed_count: summary.allowed_count, + registered_count: summary.registered_count, + }, + schedule: { + activation: { + bitcoin_height: summary.bond_start_height, + pox_cycle: summary.first_reward_cycle, + }, + unlock: { + bitcoin_height: summary.unlock_burn_height, + pox_cycle: summary.unlock_cycle, + }, + }, + balances: { + locked: { + btc: summary.btc_locked, + stx: summary.stx_locked, + }, + paid_out: { + btc: summary.btc_paid_out, + }, + }, + }; +} + +/** + * Serializes a database bond to a API bond. + * @param bond - The database bond to serialize. + * @returns The API bond. + */ +export function serializeDbBond(bond: DbBond, currentBurnBlockHeight: number): Bond { + return { + ...serializeDbBondSummary(bond, currentBurnBlockHeight), + transaction: { + tx_id: bond.tx_id, + block: { + height: bond.block_height, + hash: bond.block_hash, + index_hash: bond.index_block_hash, + time: bond.block_time, + tx_index: bond.tx_index, + }, + bitcoin_block: { + height: bond.burn_block_height, + time: bond.burn_block_time, + }, + }, + }; +} + +export function serializeDbBondAllowlistEntry(entry: DbBondAllowlistEntry): BondAllowlist { + return { + staker: entry.staker, + max_sats: entry.max_sats, + }; +} + +export function serializeDbBondRegistration(entry: DbBondRegistration): BondRegistration { + return { + bond_index: entry.bond_index, + signer: entry.signer, + staker: entry.staker, + amount_ustx: entry.amount_ustx, + sats_total: entry.sats_total, + first_reward_cycle: entry.first_reward_cycle, + unlock_burn_height: entry.unlock_burn_height, + unlock_cycle: entry.unlock_cycle, + is_l1_lock: entry.is_l1_lock, + }; +} diff --git a/src/api/serializers/v3/mempool-transactions.ts b/src/api/serializers/v3/mempool-transactions.ts index b7a9f58c0c..f4410dd371 100644 --- a/src/api/serializers/v3/mempool-transactions.ts +++ b/src/api/serializers/v3/mempool-transactions.ts @@ -23,7 +23,7 @@ import { } from '../../schemas/v3/entities/mempool-transactions.js'; import { TransactionIncludeField } from '../../schemas/v3/entities/transactions.js'; import { serializePostCondition } from './post-conditions.js'; -import { decodeClarityValueList, decodePostConditions } from '@stacks/codec'; +import { decodeClarityValueList, decodePostConditions, memoToString } from '@stacks/codec'; /** * Parses a database mempool transaction summary status into a mempool transaction summary status. @@ -83,7 +83,12 @@ export function serializeDbMempoolTransactionSummary( token_transfer: { recipient: summary.token_transfer_recipient_address!, amount: summary.token_transfer_amount!, - memo: summary.token_transfer_memo, + memo: summary.token_transfer_memo + ? { + hex: summary.token_transfer_memo, + repr: memoToString(summary.token_transfer_memo), + } + : null, }, }; return tokenTransfer; @@ -166,7 +171,12 @@ export function serializeDbMempoolTransaction( token_transfer: { recipient: transaction.token_transfer_recipient_address!, amount: transaction.token_transfer_amount!, - memo: transaction.token_transfer_memo, + memo: transaction.token_transfer_memo + ? { + hex: transaction.token_transfer_memo, + repr: memoToString(transaction.token_transfer_memo), + } + : null, }, }; return tokenTransfer; diff --git a/src/api/serializers/v3/transaction-events.ts b/src/api/serializers/v3/transaction-events.ts new file mode 100644 index 0000000000..d2d1755e35 --- /dev/null +++ b/src/api/serializers/v3/transaction-events.ts @@ -0,0 +1,186 @@ +import { TransactionEvent } from '../../schemas/v3/entities/transaction-events.js'; +import { DbTransactionEvent } from '../../../datastore/v3/types.js'; +import { DbAssetEventTypeId, DbEventTypeId } from '../../../datastore/common.js'; +import { decodeClarityValueToRepr, memoToString } from '@stacks/codec'; + +/** + * Serializes a database transaction event into a transaction event. + * @param event - The database transaction event to serialize. + * @returns The serialized transaction event. + */ +export function serializeDbTransactionEvent(event: DbTransactionEvent): TransactionEvent { + switch (event.event_type_id) { + case DbEventTypeId.SmartContractLog: { + return { + event_index: event.event_index, + type: 'contract_log', + contract_log: { + contract_id: event.contract_identifier!, + topic: 'print', + value: { + hex: event.value!, + repr: decodeClarityValueToRepr(event.value!), + }, + }, + }; + } + case DbEventTypeId.StxAsset: { + switch (event.asset_event_type_id) { + case DbAssetEventTypeId.Transfer: { + return { + event_index: event.event_index, + type: 'stx_asset', + stx_asset: { + type: 'transfer', + sender: event.sender!, + recipient: event.recipient!, + amount: event.amount, + memo: event.memo + ? { + hex: event.memo, + repr: memoToString(event.memo), + } + : null, + }, + }; + } + case DbAssetEventTypeId.Mint: { + return { + event_index: event.event_index, + type: 'stx_asset', + stx_asset: { + type: 'mint', + recipient: event.recipient!, + amount: event.amount, + }, + }; + } + case DbAssetEventTypeId.Burn: { + return { + event_index: event.event_index, + type: 'stx_asset', + stx_asset: { + type: 'burn', + sender: event.sender!, + amount: event.amount, + }, + }; + } + default: { + throw new Error(`Unexpected asset_event_type_id in: ${JSON.stringify(event)}`); + } + } + } + case DbEventTypeId.FungibleTokenAsset: { + switch (event.asset_event_type_id) { + case DbAssetEventTypeId.Transfer: { + return { + event_index: event.event_index, + type: 'ft_asset', + ft_asset: { + type: 'transfer', + asset_identifier: event.asset_identifier!, + sender: event.sender!, + recipient: event.recipient!, + amount: event.amount, + }, + }; + } + case DbAssetEventTypeId.Mint: { + return { + event_index: event.event_index, + type: 'ft_asset', + ft_asset: { + type: 'mint', + recipient: event.recipient!, + asset_identifier: event.asset_identifier!, + amount: event.amount, + }, + }; + } + case DbAssetEventTypeId.Burn: { + return { + event_index: event.event_index, + type: 'ft_asset', + ft_asset: { + type: 'burn', + sender: event.sender!, + asset_identifier: event.asset_identifier!, + amount: event.amount, + }, + }; + } + default: { + throw new Error(`Unexpected asset_event_type_id in: ${JSON.stringify(event)}`); + } + } + } + case DbEventTypeId.NonFungibleTokenAsset: { + switch (event.asset_event_type_id) { + case DbAssetEventTypeId.Transfer: { + return { + event_index: event.event_index, + type: 'nft_asset', + nft_asset: { + type: 'transfer', + asset_identifier: event.asset_identifier!, + sender: event.sender!, + recipient: event.recipient!, + value: { + hex: event.value!, + repr: decodeClarityValueToRepr(event.value!), + }, + }, + }; + } + case DbAssetEventTypeId.Mint: { + return { + event_index: event.event_index, + type: 'nft_asset', + nft_asset: { + type: 'mint', + recipient: event.recipient!, + asset_identifier: event.asset_identifier!, + value: { + hex: event.value!, + repr: decodeClarityValueToRepr(event.value!), + }, + }, + }; + } + case DbAssetEventTypeId.Burn: { + return { + event_index: event.event_index, + type: 'nft_asset', + nft_asset: { + type: 'burn', + sender: event.sender!, + asset_identifier: event.asset_identifier!, + value: { + hex: event.value!, + repr: decodeClarityValueToRepr(event.value!), + }, + }, + }; + } + default: { + throw new Error(`Unexpected asset_event_type_id in: ${JSON.stringify(event)}`); + } + } + } + case DbEventTypeId.StxLock: { + return { + event_index: event.event_index, + type: 'stx_lock', + stx_lock: { + amount: event.amount, + unlock_bitcoin_height: event.unlock_height!, + address: event.sender!, + }, + }; + } + default: { + throw new Error(`Unexpected event_type_id in: ${JSON.stringify(event)}`); + } + } +} diff --git a/src/api/serializers/v3/transactions.ts b/src/api/serializers/v3/transactions.ts index 587de28e8a..0f473a1fe4 100644 --- a/src/api/serializers/v3/transactions.ts +++ b/src/api/serializers/v3/transactions.ts @@ -34,6 +34,7 @@ import { decodeClarityValueList, decodeClarityValueToRepr, decodePostConditions, + memoToString, } from '@stacks/codec'; import { serializePostCondition } from './post-conditions.js'; import { serializeDbMempoolTransaction } from './mempool-transactions.js'; @@ -123,7 +124,12 @@ export function serializeDbTransactionSummary(summary: DbTransactionSummary): Tr token_transfer: { recipient: summary.token_transfer_recipient_address!, amount: summary.token_transfer_amount!, - memo: summary.token_transfer_memo, + memo: summary.token_transfer_memo + ? { + hex: summary.token_transfer_memo, + repr: memoToString(summary.token_transfer_memo), + } + : null, }, }; return tokenTransfer; @@ -258,7 +264,12 @@ export function serializeDbTransaction( token_transfer: { recipient: transaction.token_transfer_recipient_address!, amount: transaction.token_transfer_amount!, - memo: transaction.token_transfer_memo, + memo: transaction.token_transfer_memo + ? { + hex: transaction.token_transfer_memo, + repr: memoToString(transaction.token_transfer_memo), + } + : null, }, }; return tokenTransfer; diff --git a/src/datastore/common.ts b/src/datastore/common.ts index 18aa7d3696..59e155fe28 100644 --- a/src/datastore/common.ts +++ b/src/datastore/common.ts @@ -1,5 +1,5 @@ +import { Pox4Event, Pox5Event } from '@stacks/codec'; import { Block } from '../api/schemas/v1/entities/block.js'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; import { PgBytea, PgJsonb, PgNumeric } from '@stacks/api-toolkit'; export interface DbBlock { @@ -388,163 +388,11 @@ export interface DbEventBase { canonical: boolean; } -export type PoxSyntheticEventTable = 'pox2_events' | 'pox3_events' | 'pox4_events'; +export type Pox4SyntheticEventTable = 'pox2_events' | 'pox3_events' | 'pox4_events'; -export interface DbPoxSyntheticBaseEventData { - stacker: string; - locked: bigint; - balance: bigint; - burnchain_unlock_height: bigint; - pox_addr: string | null; - pox_addr_raw: string | null; -} +export type DbPox4SyntheticEvent = DbEventBase & Pox4Event; -export interface DbPoxSyntheticHandleUnlockEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.HandleUnlock; - data: { - first_cycle_locked: bigint; - first_unlocked_cycle: bigint; - }; -} - -export interface DbPoxSyntheticStackStxEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackStx; - data: { - lock_amount: bigint; - lock_period: bigint; - start_burn_height: bigint; - unlock_burn_height: bigint; - signer_key: string | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticStackIncreaseEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackIncrease; - data: { - increase_by: bigint; - total_locked: bigint; - signer_key: string | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticStackExtendEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackExtend; - data: { - extend_count: bigint; - unlock_burn_height: bigint; - signer_key: string | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticDelegateStxEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.DelegateStx; - data: { - amount_ustx: bigint; - delegate_to: string; - unlock_burn_height: bigint | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticDelegateStackStxEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.DelegateStackStx; - data: { - lock_amount: bigint; - unlock_burn_height: bigint; - start_burn_height: bigint; - lock_period: bigint; - delegator: string; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticDelegateStackIncreaseEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.DelegateStackIncrease; - data: { - increase_by: bigint; - total_locked: bigint; - delegator: string; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticDelegateStackExtendEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.DelegateStackExtend; - data: { - unlock_burn_height: bigint; - extend_count: bigint; - delegator: string; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticStackAggregationCommitEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackAggregationCommit; - data: { - reward_cycle: bigint; - amount_ustx: bigint; - signer_key: string | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticStackAggregationCommitIndexedEvent - extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackAggregationCommitIndexed; - data: { - reward_cycle: bigint; - amount_ustx: bigint; - signer_key: string | null; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticStackAggregationIncreaseEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.StackAggregationIncrease; - data: { - reward_cycle: bigint; - amount_ustx: bigint; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export interface DbPoxSyntheticRevokeDelegateStxEvent extends DbPoxSyntheticBaseEventData { - name: SyntheticPoxEventName.RevokeDelegateStx; - data: { - delegate_to: string; - end_cycle_id: bigint | null; - start_cycle_id: bigint | null; - }; -} - -export type DbPoxSyntheticEventData = - | DbPoxSyntheticHandleUnlockEvent - | DbPoxSyntheticStackStxEvent - | DbPoxSyntheticStackIncreaseEvent - | DbPoxSyntheticStackExtendEvent - | DbPoxSyntheticDelegateStxEvent - | DbPoxSyntheticDelegateStackStxEvent - | DbPoxSyntheticDelegateStackIncreaseEvent - | DbPoxSyntheticDelegateStackExtendEvent - | DbPoxSyntheticStackAggregationCommitEvent - | DbPoxSyntheticStackAggregationCommitIndexedEvent - | DbPoxSyntheticStackAggregationIncreaseEvent - | DbPoxSyntheticRevokeDelegateStxEvent; - -export type DbPoxSyntheticEvent = DbEventBase & DbPoxSyntheticEventData; +export type DbPox5SyntheticEvent = DbEventBase & Pox5Event; export interface DbPoxStacker { stacker: string; @@ -660,6 +508,7 @@ export interface DataStoreBlockUpdateData { pox_v1_unlock_height?: number; pox_v2_unlock_height?: number; pox_v3_unlock_height?: number; + pox_v4_unlock_height?: number; poxSetSigners?: DbPoxSetSigners; } @@ -678,9 +527,10 @@ export interface DataStoreTxEventData { smartContracts: DbSmartContract[]; names: DbBnsName[]; namespaces: DbBnsNamespace[]; - pox2Events: DbPoxSyntheticEvent[]; - pox3Events: DbPoxSyntheticEvent[]; - pox4Events: DbPoxSyntheticEvent[]; + pox2Events: DbPox4SyntheticEvent[]; + pox3Events: DbPox4SyntheticEvent[]; + pox4Events: DbPox4SyntheticEvent[]; + pox5Events: DbPox5SyntheticEvent[]; } export interface DataStoreAttachmentData { @@ -1455,7 +1305,7 @@ export interface PoxSyntheticEventQueryResult { start_cycle_id?: string | null; } -export interface PoxSyntheticEventInsertValues { +export interface Pox4SyntheticEventInsertValues { event_index: number; tx_id: PgBytea; tx_index: number; @@ -1520,6 +1370,12 @@ export interface PoxSyntheticEventInsertValues { start_cycle_id?: PgNumeric | null; } +export interface Pox5SyntheticEventInsertValues extends DbTxLocation { + event_index: number; + name: string; + data: PgJsonb; +} + export interface NftEventInsertValues { event_index: number; tx_id: PgBytea; @@ -1762,6 +1618,7 @@ export interface DbChainTip { tx_count: number; tx_count_unanchored: number; mempool_tx_count: number; + bond_count: number; } export interface DbSmartContractStatus { @@ -1770,3 +1627,74 @@ export interface DbSmartContractStatus { status: DbTxStatus; block_height?: number; } + +export interface DbTxLocation { + tx_id: string; + tx_index: number; + block_height: number; + index_block_hash: string; + parent_index_block_hash: string; + microblock_hash: string; + microblock_sequence: number; + microblock_canonical: boolean; + canonical: boolean; +} + +export interface DbBondInsertValues extends DbTxLocation { + bond_index: number; + target_rate: number; + stx_value_ratio: number; + min_ustx_ratio: number; + first_reward_cycle: number; + bond_start_height: number; + unlock_cycle: number; + unlock_burn_height: number; + early_unlock_bytes: string; + early_unlock_admin: string; +} + +export interface DbBondRegistrationInsertValues extends DbTxLocation { + bond_index: number; + signer: string; + staker: string; + amount_ustx: string; + sats_total: string; + first_reward_cycle: number; + unlock_burn_height: number; + unlock_cycle: number; + is_l1_lock: boolean; +} + +export interface DbBondAllowlistEntryInsertValues extends DbTxLocation { + bond_index: number; + staker: string; + max_sats: string; +} + +export enum DbPrincipalBondPositionStatus { + Enrolled = 0, + Running = 1, + Unlocked = 2, + EarlyExit = 3, +} + +export interface DbPrincipalBondPositionInsertValues extends DbTxLocation { + principal: string; + bond_index: number; + status: DbPrincipalBondPositionStatus; + active: boolean; + btc_locked: string; + stx_locked: string; + btc_paid_out: string; +} + +export interface DbBondRewardDistributionInsertValues extends DbTxLocation { + bond_index: number; + remaining_rewards: string; + accrued_rewards: string; + new_reserve: string; + stx_staker_rewards: string; + stx_cycle: number; + cycle_staked_ustx: string; + next_rewards_per_ustx: string; +} diff --git a/src/datastore/helpers.ts b/src/datastore/helpers.ts index 6a0feb3206..ae1da21a59 100644 --- a/src/datastore/helpers.ts +++ b/src/datastore/helpers.ts @@ -15,19 +15,7 @@ import { DbMempoolTxRaw, DbMicroblock, DbNftEvent, - DbPoxSyntheticBaseEventData, - DbPoxSyntheticDelegateStackExtendEvent, - DbPoxSyntheticDelegateStackIncreaseEvent, - DbPoxSyntheticDelegateStackStxEvent, - DbPoxSyntheticDelegateStxEvent, - DbPoxSyntheticEvent, - DbPoxSyntheticHandleUnlockEvent, - DbPoxSyntheticStackAggregationCommitEvent, - DbPoxSyntheticStackAggregationCommitIndexedEvent, - DbPoxSyntheticStackAggregationIncreaseEvent, - DbPoxSyntheticStackExtendEvent, - DbPoxSyntheticStackIncreaseEvent, - DbPoxSyntheticStackStxEvent, + DbPox4SyntheticEvent, DbSmartContract, DbSmartContractEvent, DbStxEvent, @@ -42,7 +30,6 @@ import { MicroblockQueryResult, PoxSyntheticEventQueryResult, TxQueryResult, - DbPoxSyntheticRevokeDelegateStxEvent, ReOrgUpdatedEntities, AddressTransfersTxQueryResult, DbTxWithAddressTransfers, @@ -51,16 +38,31 @@ import { CoreNodeParsedTxMessage } from '../event-stream/core-node-message.js'; import { decodeClarityValueToRepr, PostConditionAuthFlag, + Pox4EventName, PrincipalTypeID, TxPayloadTypeID, } from '@stacks/codec'; -import type { DecodedTxResult } from '@stacks/codec'; +import type { + DecodedTxResult, + Pox4EventBase, + Pox4EventDelegateStackExtend, + Pox4EventDelegateStackIncrease, + Pox4EventDelegateStackStx, + Pox4EventDelegateStx, + Pox4EventHandleUnlock, + Pox4EventRevokeDelegateStx, + Pox4EventStackAggregationCommit, + Pox4EventStackAggregationCommitIndexed, + Pox4EventStackAggregationIncrease, + Pox4EventStackExtend, + Pox4EventStackIncrease, + Pox4EventStackStx, +} from '@stacks/codec'; import { getTxSenderAddress } from '../event-stream/reader.js'; import postgres from 'postgres'; import * as prom from 'prom-client'; import { getAssetEventTypeString } from '../api/controllers/db-controller.js'; import { PgStoreEventEmitter } from './pg-store-event-emitter.js'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; import { logger, PgSqlClient } from '@stacks/api-toolkit'; import PQueue from 'p-queue'; import { DropMempoolTxReasonType, NewBlockTransactionStatus } from '@stacks/node-publisher-client'; @@ -646,7 +648,7 @@ export function parseDbEvents( return events; } -export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbPoxSyntheticEvent { +export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbPox4SyntheticEvent { const baseEvent: DbEventBase = { event_index: row.event_index, tx_id: row.tx_id, @@ -654,23 +656,24 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP block_height: row.block_height, canonical: row.canonical, }; - const basePoxEvent: DbPoxSyntheticBaseEventData = { + const basePoxEvent: Pox4EventBase = { + pox_version: 'pox4', stacker: row.stacker, - locked: BigInt(row.locked ?? 0), - balance: BigInt(row.balance), - burnchain_unlock_height: BigInt(row.burnchain_unlock_height), + locked: row.locked ?? 0, + balance: row.balance, + burnchain_unlock_height: row.burnchain_unlock_height, pox_addr: row.pox_addr ?? null, pox_addr_raw: row.pox_addr_raw ?? null, }; - const rowName = row.name as SyntheticPoxEventName; + const rowName = row.name as Pox4EventName; switch (rowName) { - case SyntheticPoxEventName.HandleUnlock: { - const eventData: DbPoxSyntheticHandleUnlockEvent = { + case Pox4EventName.HandleUnlock: { + const eventData: Pox4EventHandleUnlock = { ...basePoxEvent, name: rowName, data: { - first_cycle_locked: BigInt(unwrapOptionalProp(row, 'first_unlocked_cycle')), - first_unlocked_cycle: BigInt(unwrapOptionalProp(row, 'first_unlocked_cycle')), + first_cycle_locked: unwrapOptionalProp(row, 'first_unlocked_cycle'), + first_unlocked_cycle: unwrapOptionalProp(row, 'first_unlocked_cycle'), }, }; return { @@ -678,18 +681,18 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackStx: { - const eventData: DbPoxSyntheticStackStxEvent = { + case Pox4EventName.StackStx: { + const eventData: Pox4EventStackStx = { ...basePoxEvent, name: rowName, data: { - lock_amount: BigInt(unwrapOptionalProp(row, 'lock_amount')), - lock_period: BigInt(unwrapOptionalProp(row, 'lock_period')), - start_burn_height: BigInt(unwrapOptionalProp(row, 'start_burn_height')), - unlock_burn_height: BigInt(unwrapOptionalProp(row, 'unlock_burn_height')), + lock_amount: unwrapOptionalProp(row, 'lock_amount'), + lock_period: unwrapOptionalProp(row, 'lock_period'), + start_burn_height: unwrapOptionalProp(row, 'start_burn_height'), + unlock_burn_height: unwrapOptionalProp(row, 'unlock_burn_height'), signer_key: row.signer_key ?? null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -697,16 +700,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackIncrease: { - const eventData: DbPoxSyntheticStackIncreaseEvent = { + case Pox4EventName.StackIncrease: { + const eventData: Pox4EventStackIncrease = { ...basePoxEvent, name: rowName, data: { - increase_by: BigInt(unwrapOptionalProp(row, 'increase_by')), - total_locked: BigInt(unwrapOptionalProp(row, 'total_locked')), + increase_by: unwrapOptionalProp(row, 'increase_by'), + total_locked: unwrapOptionalProp(row, 'total_locked'), signer_key: row.signer_key ?? null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -714,16 +717,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackExtend: { - const eventData: DbPoxSyntheticStackExtendEvent = { + case Pox4EventName.StackExtend: { + const eventData: Pox4EventStackExtend = { ...basePoxEvent, name: rowName, data: { - extend_count: BigInt(unwrapOptionalProp(row, 'extend_count')), - unlock_burn_height: BigInt(unwrapOptionalProp(row, 'unlock_burn_height')), + extend_count: unwrapOptionalProp(row, 'extend_count'), + unlock_burn_height: unwrapOptionalProp(row, 'unlock_burn_height'), signer_key: row.signer_key ?? null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -731,18 +734,18 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.DelegateStx: { - const eventData: DbPoxSyntheticDelegateStxEvent = { + case Pox4EventName.DelegateStx: { + const eventData: Pox4EventDelegateStx = { ...basePoxEvent, name: rowName, data: { - amount_ustx: BigInt(unwrapOptionalProp(row, 'amount_ustx')), + amount_ustx: unwrapOptionalProp(row, 'amount_ustx'), delegate_to: unwrapOptionalProp(row, 'delegate_to'), unlock_burn_height: row.unlock_burn_height - ? BigInt(unwrapOptionalProp(row, 'unlock_burn_height')) + ? unwrapOptionalProp(row, 'unlock_burn_height') : null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -750,18 +753,18 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.DelegateStackStx: { - const eventData: DbPoxSyntheticDelegateStackStxEvent = { + case Pox4EventName.DelegateStackStx: { + const eventData: Pox4EventDelegateStackStx = { ...basePoxEvent, name: rowName, data: { - lock_amount: BigInt(unwrapOptionalProp(row, 'lock_amount')), - unlock_burn_height: BigInt(unwrapOptionalProp(row, 'unlock_burn_height')), - start_burn_height: BigInt(unwrapOptionalProp(row, 'start_burn_height')), - lock_period: BigInt(unwrapOptionalProp(row, 'lock_period')), + lock_amount: unwrapOptionalProp(row, 'lock_amount'), + unlock_burn_height: unwrapOptionalProp(row, 'unlock_burn_height'), + start_burn_height: unwrapOptionalProp(row, 'start_burn_height'), + lock_period: unwrapOptionalProp(row, 'lock_period'), delegator: unwrapOptionalProp(row, 'delegator'), - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -769,16 +772,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.DelegateStackIncrease: { - const eventData: DbPoxSyntheticDelegateStackIncreaseEvent = { + case Pox4EventName.DelegateStackIncrease: { + const eventData: Pox4EventDelegateStackIncrease = { ...basePoxEvent, name: rowName, data: { - increase_by: BigInt(unwrapOptionalProp(row, 'increase_by')), - total_locked: BigInt(unwrapOptionalProp(row, 'total_locked')), + increase_by: unwrapOptionalProp(row, 'increase_by'), + total_locked: unwrapOptionalProp(row, 'total_locked'), delegator: unwrapOptionalProp(row, 'delegator'), - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -786,16 +789,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.DelegateStackExtend: { - const eventData: DbPoxSyntheticDelegateStackExtendEvent = { + case Pox4EventName.DelegateStackExtend: { + const eventData: Pox4EventDelegateStackExtend = { ...basePoxEvent, name: rowName, data: { - unlock_burn_height: BigInt(unwrapOptionalProp(row, 'unlock_burn_height')), - extend_count: BigInt(unwrapOptionalProp(row, 'extend_count')), + unlock_burn_height: unwrapOptionalProp(row, 'unlock_burn_height'), + extend_count: unwrapOptionalProp(row, 'extend_count'), delegator: unwrapOptionalProp(row, 'delegator'), - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -803,16 +806,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackAggregationCommit: { - const eventData: DbPoxSyntheticStackAggregationCommitEvent = { + case Pox4EventName.StackAggregationCommit: { + const eventData: Pox4EventStackAggregationCommit = { ...basePoxEvent, name: rowName, data: { - reward_cycle: BigInt(unwrapOptionalProp(row, 'reward_cycle')), - amount_ustx: BigInt(unwrapOptionalProp(row, 'amount_ustx')), + reward_cycle: unwrapOptionalProp(row, 'reward_cycle'), + amount_ustx: unwrapOptionalProp(row, 'amount_ustx'), signer_key: row.signer_key ?? null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -820,16 +823,16 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackAggregationCommitIndexed: { - const eventData: DbPoxSyntheticStackAggregationCommitIndexedEvent = { + case Pox4EventName.StackAggregationCommitIndexed: { + const eventData: Pox4EventStackAggregationCommitIndexed = { ...basePoxEvent, name: rowName, data: { - reward_cycle: BigInt(unwrapOptionalProp(row, 'reward_cycle')), - amount_ustx: BigInt(unwrapOptionalProp(row, 'amount_ustx')), + reward_cycle: unwrapOptionalProp(row, 'reward_cycle'), + amount_ustx: unwrapOptionalProp(row, 'amount_ustx'), signer_key: row.signer_key ?? null, - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -837,15 +840,15 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.StackAggregationIncrease: { - const eventData: DbPoxSyntheticStackAggregationIncreaseEvent = { + case Pox4EventName.StackAggregationIncrease: { + const eventData: Pox4EventStackAggregationIncrease = { ...basePoxEvent, name: rowName, data: { - reward_cycle: BigInt(unwrapOptionalProp(row, 'reward_cycle')), - amount_ustx: BigInt(unwrapOptionalProp(row, 'amount_ustx')), - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + reward_cycle: unwrapOptionalProp(row, 'reward_cycle'), + amount_ustx: unwrapOptionalProp(row, 'amount_ustx'), + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -853,14 +856,14 @@ export function parseDbPoxSyntheticEvent(row: PoxSyntheticEventQueryResult): DbP ...eventData, }; } - case SyntheticPoxEventName.RevokeDelegateStx: { - const eventData: DbPoxSyntheticRevokeDelegateStxEvent = { + case Pox4EventName.RevokeDelegateStx: { + const eventData: Pox4EventRevokeDelegateStx = { ...basePoxEvent, name: rowName, data: { delegate_to: unwrapOptionalProp(row, 'delegate_to'), - end_cycle_id: row.end_cycle_id ? BigInt(row.end_cycle_id) : null, - start_cycle_id: row.start_cycle_id ? BigInt(row.start_cycle_id) : null, + end_cycle_id: row.end_cycle_id ? row.end_cycle_id : null, + start_cycle_id: row.start_cycle_id ? row.start_cycle_id : null, }, }; return { @@ -1305,6 +1308,7 @@ export function markBlockUpdateDataAsNonCanonical(data: DataStoreBlockUpdateData pox2Events: tx.pox2Events.map(e => ({ ...e, canonical: false })), pox3Events: tx.pox3Events.map(e => ({ ...e, canonical: false })), pox4Events: tx.pox4Events.map(e => ({ ...e, canonical: false })), + pox5Events: tx.pox5Events.map(e => ({ ...e, canonical: false })), })); data.minerRewards = data.minerRewards.map(mr => ({ ...mr, canonical: false })); } diff --git a/src/datastore/pg-store-v2.ts b/src/datastore/pg-store-v2.ts index a129c0dc45..d8b13ed1af 100644 --- a/src/datastore/pg-store-v2.ts +++ b/src/datastore/pg-store-v2.ts @@ -48,7 +48,7 @@ import { parseDbPoxSyntheticEvent, prefixedCols, } from './helpers.js'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; +import { Pox4EventName } from '@stacks/codec'; async function assertTxIdExists(sql: PgSqlClient, tx_id: string) { const txCheck = await sql`SELECT tx_id FROM txs WHERE tx_id = ${tx_id} LIMIT 1`; @@ -1183,41 +1183,45 @@ export class PgStoreV2 extends BasePgStoreModule { let burnchainLockHeight = 0; let burnchainUnlockHeight = 0; - // == PoX-4 ================================================================ + const [poxState] = await sql<{ pox_v4_unlock_height: string }[]>` + SELECT pox_v4_unlock_height + FROM pox_state + LIMIT 1 + `; + const pox4UnlockHeight = parseInt(poxState?.pox_v4_unlock_height ?? '0') || null; + const includePox4State = !pox4UnlockHeight || burnBlockHeight <= pox4UnlockHeight; + // Query for the latest lock event that still applies to the current burn block height. // Special case for `handle-unlock` which should be returned if it is the last received event. + if (includePox4State) { + const pox4EventQuery = await sql` + SELECT ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} + AND block_height <= ${blockHeight} + AND ( + (name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${burnBlockHeight}) + OR + (name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${burnBlockHeight}) + ) + ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + LIMIT 1 + `; + if (pox4EventQuery.length > 0) { + const pox4Event = parseDbPoxSyntheticEvent(pox4EventQuery[0]); + if (pox4Event.name !== Pox4EventName.HandleUnlock) { + lockTxId = pox4Event.tx_id; + locked = BigInt(pox4Event.locked); + burnchainUnlockHeight = Number(pox4Event.burnchain_unlock_height); + lockHeight = pox4Event.block_height; - const pox4EventQuery = await sql` - SELECT ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} - FROM pox4_events - WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} - AND block_height <= ${blockHeight} - AND ( - (name != ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height >= ${burnBlockHeight}) - OR - (name = ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height < ${burnBlockHeight}) - ) - ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC - LIMIT 1 - `; - if (pox4EventQuery.length > 0) { - const pox4Event = parseDbPoxSyntheticEvent(pox4EventQuery[0]); - if (pox4Event.name !== SyntheticPoxEventName.HandleUnlock) { - lockTxId = pox4Event.tx_id; - locked = pox4Event.locked; - burnchainUnlockHeight = Number(pox4Event.burnchain_unlock_height); - lockHeight = pox4Event.block_height; - - const [burnBlockQuery] = await sql<{ burn_block_height: string }[]>` - SELECT burn_block_height FROM blocks - WHERE block_height = ${blockHeight} AND canonical = true - LIMIT 1 - `; - burnchainLockHeight = parseInt(burnBlockQuery?.burn_block_height ?? '0'); + const [burnBlockQuery] = await sql<{ burn_block_height: string }[]>` + SELECT burn_block_height FROM blocks + WHERE block_height = ${blockHeight} AND canonical = true + LIMIT 1 + `; + burnchainLockHeight = parseInt(burnBlockQuery?.burn_block_height ?? '0'); + } } } diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 791719ae59..30c2138022 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -61,9 +61,9 @@ import { RawTxQueryResult, StxUnlockEvent, TransferQueryResult, - PoxSyntheticEventTable, + Pox4SyntheticEventTable, DbPoxStacker, - DbPoxSyntheticEvent, + DbPox4SyntheticEvent, } from './common.js'; import { abiColumn, @@ -86,7 +86,6 @@ import { validateZonefileHash, } from './helpers.js'; import { PgNotifier } from './pg-notifier.js'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; import { BasePgStore, PgSqlClient, PgSqlQuery, connectPostgres } from '@stacks/api-toolkit'; import { getConnectionArgs, getConnectionConfig } from './connection.js'; import * as path from 'path'; @@ -94,6 +93,7 @@ import { PgStoreV2 } from './pg-store-v2.js'; import { ENV } from '../env.js'; import { BlockIdParam } from '../api/routes/v2/schemas.js'; import { PgStoreV3 } from './v3/pg-store-v3.js'; +import { Pox4EventName } from '@stacks/codec'; export const MIGRATIONS_DIR = path.join(REPO_DIR, 'migrations'); @@ -213,6 +213,7 @@ export class PgStore extends BasePgStore { tx_count: tip?.tx_count ?? 0, tx_count_unanchored: tip?.tx_count_unanchored ?? 0, mempool_tx_count: tip?.mempool_tx_count ?? 0, + bond_count: tip?.bond_count ?? 0, }; } @@ -289,14 +290,20 @@ export class PgStore extends BasePgStore { pox1UnlockHeight: number | null; pox2UnlockHeight: number | null; pox3UnlockHeight: number | null; + pox4UnlockHeight: number | null; }> > { const query = await sql< - { pox_v1_unlock_height: string; pox_v2_unlock_height: string; pox_v3_unlock_height: string }[] + { + pox_v1_unlock_height: string; + pox_v2_unlock_height: string; + pox_v3_unlock_height: string; + pox_v4_unlock_height: string; + }[] >` - SELECT pox_v1_unlock_height, pox_v2_unlock_height, pox_v3_unlock_height + SELECT pox_v1_unlock_height, pox_v2_unlock_height, pox_v3_unlock_height, pox_v4_unlock_height FROM pox_state - LIMIt 1 + LIMIT 1 `; if (query.length === 0) { return { found: false }; @@ -304,10 +311,14 @@ export class PgStore extends BasePgStore { const pox1UnlockHeight = parseInt(query[0].pox_v1_unlock_height) || null; const pox2UnlockHeight = parseInt(query[0].pox_v2_unlock_height) || null; const pox3UnlockHeight = parseInt(query[0].pox_v3_unlock_height) || null; + const pox4UnlockHeight = parseInt(query[0].pox_v4_unlock_height) || null; if (pox2UnlockHeight === 0) { return { found: false }; } - return { found: true, result: { pox1UnlockHeight, pox2UnlockHeight, pox3UnlockHeight } }; + return { + found: true, + result: { pox1UnlockHeight, pox2UnlockHeight, pox3UnlockHeight, pox4UnlockHeight }, + }; } async getPoxForceUnlockHeights() { @@ -1676,7 +1687,7 @@ export class PgStore extends BasePgStore { event_index, tx_id, tx_index, block_height, canonical, locked_amount, unlock_height, locked_address, contract_name FROM stx_lock_events WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash} - AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} + AND canonical = true AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const stxResults = await sql< { @@ -1696,7 +1707,7 @@ export class PgStore extends BasePgStore { event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount, memo FROM stx_events WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash} - AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} + AND canonical = true AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const ftResults = await sql< { @@ -1716,7 +1727,7 @@ export class PgStore extends BasePgStore { event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, asset_identifier, amount FROM ft_events WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash} - AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} + AND canonical = true AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const nftResults = await sql< { @@ -1736,7 +1747,7 @@ export class PgStore extends BasePgStore { event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, asset_identifier, value FROM nft_events WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash} - AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} + AND canonical = true AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const logResults = await sql< { @@ -1754,7 +1765,7 @@ export class PgStore extends BasePgStore { event_index, tx_id, tx_index, block_height, canonical, contract_identifier, topic, value FROM contract_logs WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash} - AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} + AND canonical = true AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; return { results: parseDbEvents(stxLockResults, stxResults, ftResults, nftResults, logResults), @@ -1961,8 +1972,8 @@ export class PgStore extends BasePgStore { }: { limit: number; offset: number; - poxTable: PoxSyntheticEventTable; - }): Promise { + poxTable: Pox4SyntheticEventTable; + }): Promise { return await this.sqlTransaction(async sql => { const cols = poxTable === 'pox4_events' ? POX4_SYNTHETIC_EVENT_COLUMNS : POX_SYNTHETIC_EVENT_COLUMNS; @@ -1984,8 +1995,8 @@ export class PgStore extends BasePgStore { poxTable, }: { txId: string; - poxTable: PoxSyntheticEventTable; - }): Promise> { + poxTable: Pox4SyntheticEventTable; + }): Promise> { return await this.sqlTransaction(async sql => { const dbTx = await this.getTx({ txId, includeUnanchored: true }); if (!dbTx.found) { @@ -2009,8 +2020,8 @@ export class PgStore extends BasePgStore { poxTable, }: { principal: string; - poxTable: PoxSyntheticEventTable; - }): Promise> { + poxTable: Pox4SyntheticEventTable; + }): Promise> { return await this.sqlTransaction(async sql => { const cols = poxTable === 'pox4_events' ? POX4_SYNTHETIC_EVENT_COLUMNS : POX_SYNTHETIC_EVENT_COLUMNS; @@ -2032,7 +2043,7 @@ export class PgStore extends BasePgStore { afterBlockHeight: number; limit: number; offset: number; - poxTable: PoxSyntheticEventTable; + poxTable: Pox4SyntheticEventTable; }): Promise> { return await this.sqlTransaction(async sql => { const queryResults = await sql< @@ -2056,8 +2067,8 @@ export class PgStore extends BasePgStore { AND microblock_canonical = true AND delegate_to = ${args.delegator} AND name IN ( - ${SyntheticPoxEventName.DelegateStx}, - ${SyntheticPoxEventName.RevokeDelegateStx} + ${Pox4EventName.DelegateStx}, + ${Pox4EventName.RevokeDelegateStx} ) AND block_height <= ${args.blockHeight} AND block_height > ${args.afterBlockHeight} @@ -2075,7 +2086,7 @@ export class PgStore extends BasePgStore { stacker, pox_addr, amount_ustx, unlock_burn_height, block_height::integer, tx_id, COUNT(*) OVER()::integer AS total_rows FROM distinct_rows - WHERE name = ${SyntheticPoxEventName.DelegateStx} + WHERE name = ${Pox4EventName.DelegateStx} ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC LIMIT ${args.limit} OFFSET ${args.offset} @@ -2273,6 +2284,7 @@ export class PgStore extends BasePgStore { let includePox1State = true; let includePox2State = true; let includePox3State = true; + let includePox4State = true; const poxForceUnlockHeights = await this.getPoxForcedUnlockHeightsInternal(sql); if (poxForceUnlockHeights.found) { if ( @@ -2293,6 +2305,12 @@ export class PgStore extends BasePgStore { ) { includePox3State = false; } + if ( + poxForceUnlockHeights.result.pox4UnlockHeight && + burnBlockHeight > poxForceUnlockHeights.result.pox4UnlockHeight + ) { + includePox4State = false; + } } // Once the pox_v1_unlock_height is reached, stop using `stx_lock_events` to determinel locked state, @@ -2337,20 +2355,16 @@ export class PgStore extends BasePgStore { WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} AND block_height <= ${blockHeight} AND ( - (name != ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height >= ${burnBlockHeight}) + (name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${burnBlockHeight}) OR - (name = ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height < ${burnBlockHeight}) + (name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${burnBlockHeight}) ) ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC LIMIT 1 `; if (pox2EventQuery.length > 0) { const pox2Event = parseDbPoxSyntheticEvent(pox2EventQuery[0]); - if (pox2Event.name === SyntheticPoxEventName.HandleUnlock) { + if (pox2Event.name === Pox4EventName.HandleUnlock) { // on a handle-unlock, set all of the locked stx related property to empty/default lockTxId = ''; locked = 0n; @@ -2359,7 +2373,7 @@ export class PgStore extends BasePgStore { burnchainLockHeight = 0; } else { lockTxId = pox2Event.tx_id; - locked = pox2Event.locked; + locked = BigInt(pox2Event.locked); burnchainUnlockHeight = Number(pox2Event.burnchain_unlock_height); lockHeight = pox2Event.block_height; const blockQuery = await this.getBlockByHeightInternal(sql, lockHeight); @@ -2378,20 +2392,16 @@ export class PgStore extends BasePgStore { WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} AND block_height <= ${blockHeight} AND ( - (name != ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height >= ${burnBlockHeight}) + (name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${burnBlockHeight}) OR - (name = ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height < ${burnBlockHeight}) + (name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${burnBlockHeight}) ) ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC LIMIT 1 `; if (pox3EventQuery.length > 0) { const pox3Event = parseDbPoxSyntheticEvent(pox3EventQuery[0]); - if (pox3Event.name === SyntheticPoxEventName.HandleUnlock) { + if (pox3Event.name === Pox4EventName.HandleUnlock) { // on a handle-unlock, set all of the locked stx related property to empty/default lockTxId = ''; locked = 0n; @@ -2400,7 +2410,7 @@ export class PgStore extends BasePgStore { burnchainLockHeight = 0; } else { lockTxId = pox3Event.tx_id; - locked = pox3Event.locked; + locked = BigInt(pox3Event.locked); burnchainUnlockHeight = Number(pox3Event.burnchain_unlock_height); lockHeight = pox3Event.block_height; const blockQuery = await this.getBlockByHeightInternal(sql, lockHeight); @@ -2409,48 +2419,42 @@ export class PgStore extends BasePgStore { } } - // == PoX-4 ================================================================ - // Assuming includePox3State = true; since there is no unlock height for pox4 (yet) - - // Query for the latest lock event that still applies to the current burn block height. - // Special case for `handle-unlock` which should be returned if it is the last received event. - - const pox4EventQuery = await sql` - SELECT ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} - FROM pox4_events - WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} - AND block_height <= ${blockHeight} - AND ( - (name != ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height >= ${burnBlockHeight}) - OR - (name = ${ - SyntheticPoxEventName.HandleUnlock - } AND burnchain_unlock_height < ${burnBlockHeight}) - ) - ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC - LIMIT 1 - `; - if (pox4EventQuery.length > 0) { - const pox4Event = parseDbPoxSyntheticEvent(pox4EventQuery[0]); - if (pox4Event.name === SyntheticPoxEventName.HandleUnlock) { - // on a handle-unlock, set all of the locked stx related property to empty/default - lockTxId = ''; - locked = 0n; - burnchainUnlockHeight = 0; - lockHeight = 0; - burnchainLockHeight = 0; - } else { - lockTxId = pox4Event.tx_id; - locked = pox4Event.locked; - burnchainUnlockHeight = Number(pox4Event.burnchain_unlock_height); - lockHeight = pox4Event.block_height; - const blockQuery = await this.getBlockByHeightInternal(sql, lockHeight); - burnchainLockHeight = blockQuery.found ? blockQuery.result.burn_block_height : 0; + // Once the pox_v4_unlock_height is reached, stop using `pox4_events` to determine locked state. + if (includePox4State) { + // Query for the latest lock event that still applies to the current burn block height. + // Special case for `handle-unlock` which should be returned if it is the last received event. + const pox4EventQuery = await sql` + SELECT ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress} + AND block_height <= ${blockHeight} + AND ( + (name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${burnBlockHeight}) + OR + (name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${burnBlockHeight}) + ) + ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + LIMIT 1 + `; + if (pox4EventQuery.length > 0) { + const pox4Event = parseDbPoxSyntheticEvent(pox4EventQuery[0]); + if (pox4Event.name === Pox4EventName.HandleUnlock) { + // on a handle-unlock, set all of the locked stx related property to empty/default + lockTxId = ''; + locked = 0n; + burnchainUnlockHeight = 0; + lockHeight = 0; + burnchainLockHeight = 0; + } else { + lockTxId = pox4Event.tx_id; + locked = BigInt(pox4Event.locked); + burnchainUnlockHeight = Number(pox4Event.burnchain_unlock_height); + lockHeight = pox4Event.block_height; + const blockQuery = await this.getBlockByHeightInternal(sql, lockHeight); + burnchainLockHeight = blockQuery.found ? blockQuery.result.burn_block_height : 0; + } } } - // ========================================================================= const minerRewardQuery = await sql<{ amount: string }[]>` SELECT sum( @@ -4157,11 +4161,13 @@ export class PgStore extends BasePgStore { let v1UnlockHeight: number | null = null; let v2UnlockHeight: number | null = null; let v3UnlockHeight: number | null = null; + let v4UnlockHeight: number | null = null; const poxUnlockHeights = await this.getPoxForcedUnlockHeightsInternal(sql); if (poxUnlockHeights.found) { v1UnlockHeight = poxUnlockHeights.result.pox1UnlockHeight; v2UnlockHeight = poxUnlockHeights.result.pox2UnlockHeight; v3UnlockHeight = poxUnlockHeights.result.pox3UnlockHeight; + v4UnlockHeight = poxUnlockHeights.result.pox4UnlockHeight; } type StxLockEventResult = { @@ -4230,15 +4236,15 @@ export class PgStore extends BasePgStore { burnchain_unlock_height <= ${current_burn_height} AND burnchain_unlock_height > ${previous_burn_height} AND name IN ${sql([ - SyntheticPoxEventName.StackStx, - SyntheticPoxEventName.StackIncrease, - SyntheticPoxEventName.StackExtend, - SyntheticPoxEventName.DelegateStackStx, - SyntheticPoxEventName.DelegateStackIncrease, - SyntheticPoxEventName.DelegateStackExtend, + Pox4EventName.StackStx, + Pox4EventName.StackIncrease, + Pox4EventName.StackExtend, + Pox4EventName.DelegateStackStx, + Pox4EventName.DelegateStackIncrease, + Pox4EventName.DelegateStackExtend, ])} ) OR ( - name = ${SyntheticPoxEventName.HandleUnlock} + name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${current_burn_height} AND burnchain_unlock_height >= ${previous_burn_height} ) @@ -4271,17 +4277,17 @@ export class PgStore extends BasePgStore { WHERE canonical = true AND microblock_canonical = true AND block_height <= ${block.block_height} AND ( - ( name != ${SyntheticPoxEventName.HandleUnlock} AND + ( name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${current_burn_height}) OR - ( name = ${SyntheticPoxEventName.HandleUnlock} AND + ( name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${current_burn_height}) ) ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC `; for (const row of pox2EventQuery) { const pox2Event = parseDbPoxSyntheticEvent(row); - if (pox2Event.name !== SyntheticPoxEventName.HandleUnlock) { + if (pox2Event.name !== Pox4EventName.HandleUnlock) { const unlockEvent: StxLockEventResult = { locked_amount: pox2Event.locked.toString(), unlock_height: Number(pox2Event.burnchain_unlock_height), @@ -4308,15 +4314,15 @@ export class PgStore extends BasePgStore { burnchain_unlock_height <= ${current_burn_height} AND burnchain_unlock_height > ${previous_burn_height} AND name IN ${sql([ - SyntheticPoxEventName.StackStx, - SyntheticPoxEventName.StackIncrease, - SyntheticPoxEventName.StackExtend, - SyntheticPoxEventName.DelegateStackStx, - SyntheticPoxEventName.DelegateStackIncrease, - SyntheticPoxEventName.DelegateStackExtend, + Pox4EventName.StackStx, + Pox4EventName.StackIncrease, + Pox4EventName.StackExtend, + Pox4EventName.DelegateStackStx, + Pox4EventName.DelegateStackIncrease, + Pox4EventName.DelegateStackExtend, ])} ) OR ( - name = ${SyntheticPoxEventName.HandleUnlock} + name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${current_burn_height} AND burnchain_unlock_height >= ${previous_burn_height} ) @@ -4350,17 +4356,17 @@ export class PgStore extends BasePgStore { WHERE canonical = true AND microblock_canonical = true AND block_height <= ${block.block_height} AND ( - ( name != ${SyntheticPoxEventName.HandleUnlock} AND + ( name != ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height >= ${current_burn_height}) OR - ( name = ${SyntheticPoxEventName.HandleUnlock} AND + ( name = ${Pox4EventName.HandleUnlock} AND burnchain_unlock_height < ${current_burn_height}) ) ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC `; for (const row of pox3EventQuery) { const pox3Event = parseDbPoxSyntheticEvent(row); - if (pox3Event.name !== SyntheticPoxEventName.HandleUnlock) { + if (pox3Event.name !== Pox4EventName.HandleUnlock) { const unlockEvent: StxLockEventResult = { locked_amount: pox3Event.locked.toString(), unlock_height: Number(pox3Event.burnchain_unlock_height), @@ -4374,45 +4380,83 @@ export class PgStore extends BasePgStore { } } - // eslint-disable-next-line no-useless-assignment let poxV4Unlocks: StxLockEventResult[] = []; - const pox4EventQuery = await sql` - SELECT DISTINCT ON (stacker) stacker, ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} - FROM pox4_events - WHERE canonical = true AND microblock_canonical = true - AND block_height <= ${block.block_height} - AND ( - ( - burnchain_unlock_height <= ${current_burn_height} - AND burnchain_unlock_height > ${previous_burn_height} - AND name IN ${sql([ - SyntheticPoxEventName.StackStx, - SyntheticPoxEventName.StackIncrease, - SyntheticPoxEventName.StackExtend, - SyntheticPoxEventName.DelegateStackStx, - SyntheticPoxEventName.DelegateStackIncrease, - SyntheticPoxEventName.DelegateStackExtend, - ])} - ) OR ( - name = ${SyntheticPoxEventName.HandleUnlock} - AND burnchain_unlock_height < ${current_burn_height} - AND burnchain_unlock_height >= ${previous_burn_height} + const checkPox4Unlocks = v4UnlockHeight === null || current_burn_height < v4UnlockHeight; + if (checkPox4Unlocks) { + const pox4EventQuery = await sql` + SELECT DISTINCT ON (stacker) stacker, ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND block_height <= ${block.block_height} + AND ( + ( + burnchain_unlock_height <= ${current_burn_height} + AND burnchain_unlock_height > ${previous_burn_height} + AND name IN ${sql([ + Pox4EventName.StackStx, + Pox4EventName.StackIncrease, + Pox4EventName.StackExtend, + Pox4EventName.DelegateStackStx, + Pox4EventName.DelegateStackIncrease, + Pox4EventName.DelegateStackExtend, + ])} + ) OR ( + name = ${Pox4EventName.HandleUnlock} + AND burnchain_unlock_height < ${current_burn_height} + AND burnchain_unlock_height >= ${previous_burn_height} + ) ) - ) - ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC - `; - poxV4Unlocks = pox4EventQuery.map(row => { - const pox4Event = parseDbPoxSyntheticEvent(row); - const unlockEvent: StxLockEventResult = { - locked_amount: pox4Event.locked.toString(), - unlock_height: Number(pox4Event.burnchain_unlock_height), - locked_address: pox4Event.stacker, - block_height: pox4Event.block_height, - tx_index: pox4Event.tx_index, - event_index: pox4Event.event_index, - }; - return unlockEvent; - }); + ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + `; + poxV4Unlocks = pox4EventQuery.map(row => { + const pox4Event = parseDbPoxSyntheticEvent(row); + const unlockEvent: StxLockEventResult = { + locked_amount: pox4Event.locked.toString(), + unlock_height: Number(pox4Event.burnchain_unlock_height), + locked_address: pox4Event.stacker, + block_height: pox4Event.block_height, + tx_index: pox4Event.tx_index, + event_index: pox4Event.event_index, + }; + return unlockEvent; + }); + } + + const poxV4ForceUnlocks: StxLockEventResult[] = []; + const generatePoxV4ForceUnlocks = + v4UnlockHeight !== null && + current_burn_height > v4UnlockHeight && + previous_burn_height <= v4UnlockHeight; + if (generatePoxV4ForceUnlocks) { + const pox4EventQuery = await sql` + SELECT DISTINCT ON (stacker) stacker, ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)} + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND block_height <= ${block.block_height} + AND ( + ( name != ${Pox4EventName.HandleUnlock} AND + burnchain_unlock_height >= ${current_burn_height}) + OR + ( name = ${Pox4EventName.HandleUnlock} AND + burnchain_unlock_height < ${current_burn_height}) + ) + ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + `; + for (const row of pox4EventQuery) { + const pox4Event = parseDbPoxSyntheticEvent(row); + if (pox4Event.name !== Pox4EventName.HandleUnlock) { + const unlockEvent: StxLockEventResult = { + locked_amount: pox4Event.locked.toString(), + unlock_height: Number(pox4Event.burnchain_unlock_height), + locked_address: pox4Event.stacker, + block_height: pox4Event.block_height, + tx_index: pox4Event.tx_index, + event_index: pox4Event.event_index, + }; + poxV4ForceUnlocks.push(unlockEvent); + } + } + } const txIdQuery = await sql<{ tx_id: string }[]>` SELECT tx_id @@ -4431,6 +4475,7 @@ export class PgStore extends BasePgStore { poxV3Unlocks, poxV3ForceUnlocks, poxV4Unlocks, + poxV4ForceUnlocks, ]) { unlocks.forEach(row => { const unlockEvent: StxUnlockEvent = { @@ -4484,7 +4529,7 @@ export class PgStore extends BasePgStore { ( SELECT CASE WHEN burnchain_unlock_height >= (SELECT burn_block_height FROM chain_tip) - AND name != ${SyntheticPoxEventName.HandleUnlock} + AND name != ${Pox4EventName.HandleUnlock} THEN 'locked' ELSE 'unlocked' END diff --git a/src/datastore/pg-write-store.ts b/src/datastore/pg-write-store.ts index 4ed3046fa3..47c7304953 100644 --- a/src/datastore/pg-write-store.ts +++ b/src/datastore/pg-write-store.ts @@ -51,19 +51,27 @@ import { DataStoreAttachmentData, DataStoreAttachmentSubdomainData, DataStoreBnsBlockData, - PoxSyntheticEventInsertValues, + Pox4SyntheticEventInsertValues, DbTxRaw, DbMempoolTxRaw, DbChainTip, NftCustodyInsertValues, DataStoreBnsBlockTxData, - DbPoxSyntheticEvent, - PoxSyntheticEventTable, + DbPox4SyntheticEvent, + Pox4SyntheticEventTable, DbPoxSetSigners, PoxSetSignerValues, PoxCycleInsertValues, DbAssetEventTypeId, DbBurnBlockPoxTx, + Pox5SyntheticEventInsertValues, + DbBondInsertValues, + DbTxLocation, + DbBondRegistrationInsertValues, + DbBondAllowlistEntryInsertValues, + DbPrincipalBondPositionInsertValues, + DbPrincipalBondPositionStatus, + DbBondRewardDistributionInsertValues, } from './common.js'; import { BLOCK_COLUMNS, @@ -84,7 +92,6 @@ import { PgNotifier } from './pg-notifier.js'; import { MIGRATIONS_DIR, PgStore } from './pg-store.js'; import * as zoneFileParser from 'zone-file'; import { parseResolver, parseZoneFileTxt } from '../event-stream/bns/bns-helpers.js'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; import { PgSqlClient, batchIterate, @@ -98,6 +105,17 @@ import { PgServer, getConnectionArgs, getConnectionConfig } from './connection.j import { BigNumber } from 'bignumber.js'; import { RedisNotifier } from './redis-notifier.js'; import { ENV } from '../env.js'; +import { + Pox4EventName, + Pox5EventAddToAllowlist, + Pox5EventAnnounceL1EarlyExit, + Pox5EventCalculateRewards, + Pox5EventName, + Pox5EventRegisterForBond, + Pox5EventSetupBond, + Pox5EventUnstakeSbtc, + Pox5EventUpdateBondRegistration, +} from '@stacks/codec'; const INSERT_BATCH_SIZE = 500; @@ -364,9 +382,10 @@ export class PgWriteStore extends PgStore { q.enqueue(() => this.updateStxEvents(sql, newTxData)); q.enqueue(() => this.updatePrincipalTxs(sql, newTxData)); q.enqueue(() => this.updateSmartContractEvents(sql, newTxData)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox2_events', newTxData)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox3_events', newTxData)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox4_events', newTxData)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox2_events', newTxData)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox3_events', newTxData)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox4_events', newTxData)); + q.enqueue(() => this.insertPox5SyntheticEvents(sql, newTxData)); q.enqueue(() => this.updateStxLockEvents(sql, newTxData)); q.enqueue(() => this.updateFtEvents(sql, newTxData)); for (const entry of newTxData) { @@ -397,6 +416,12 @@ export class PgWriteStore extends PgStore { await sql` WITH new_tx_count AS ( SELECT tx_count + ${data.txs.length} AS tx_count FROM chain_tip + ), + new_bond_count AS ( + SELECT COUNT(*)::int AS bond_count + FROM bonds + WHERE canonical = true + AND microblock_canonical = true ) UPDATE chain_tip SET block_height = ${data.block.block_height}, @@ -407,7 +432,8 @@ export class PgWriteStore extends PgStore { microblock_sequence = NULL, block_count = ${data.block.block_height}, tx_count = (SELECT tx_count FROM new_tx_count), - tx_count_unanchored = (SELECT tx_count FROM new_tx_count) + tx_count_unanchored = (SELECT tx_count FROM new_tx_count), + bond_count = (SELECT bond_count FROM new_bond_count) `; if (this.metrics) { this.metrics.blockHeight.set(data.block.block_height); @@ -501,6 +527,345 @@ export class PgWriteStore extends PgStore { return new Set(); } + private async insertPox5SyntheticEvents(sql: PgSqlClient, txs: DataStoreTxEventData[]) { + const poxValues: Pox5SyntheticEventInsertValues[] = []; + for (const tx of txs) { + if (tx.pox5Events.length === 0) continue; + const txLocation: DbTxLocation = { + tx_id: tx.tx.tx_id, + tx_index: tx.tx.tx_index, + block_height: tx.tx.block_height, + index_block_hash: tx.tx.index_block_hash, + parent_index_block_hash: tx.tx.parent_index_block_hash, + microblock_hash: tx.tx.microblock_hash, + microblock_sequence: tx.tx.microblock_sequence, + microblock_canonical: tx.tx.microblock_canonical, + canonical: tx.tx.canonical, + }; + for (const poxEvent of tx.pox5Events) { + poxValues.push({ + ...txLocation, + event_index: poxEvent.event_index, + name: poxEvent.name, + data: poxEvent.data, + }); + switch (poxEvent.name) { + case Pox5EventName.SetupBond: + await this.updateBond(sql, txLocation, poxEvent); + break; + case Pox5EventName.AddToAllowlist: + await this.updateBondAllowlistEntry(sql, txLocation, poxEvent); + break; + case Pox5EventName.RegisterForBond: + case Pox5EventName.UpdateBondRegistration: + await this.updateBondRegistration(sql, txLocation, poxEvent); + await this.updatePrincipalBondPosition(sql, txLocation, poxEvent); + break; + case Pox5EventName.AnnounceL1EarlyExit: + case Pox5EventName.UnstakeSbtc: + await this.updatePrincipalBondPosition(sql, txLocation, poxEvent); + break; + case Pox5EventName.CalculateRewards: + await this.updateBondRewardDistribution(sql, txLocation, poxEvent); + break; + case Pox5EventName.BondDistribution: + // TODO: Implement + break; + case Pox5EventName.ClaimRewards: + break; + case Pox5EventName.Stake: + case Pox5EventName.StakeUpdate: + // TODO: Implement + break; + case Pox5EventName.Unstake: + // TODO: Implement + break; + case Pox5EventName.RegisterSigner: + // TODO: Implement + break; + } + } + } + for (const batch of batchIterate(poxValues, INSERT_BATCH_SIZE)) { + await sql` + INSERT INTO pox5_events ${sql(batch)} + `; + } + } + + private async updateBond(sql: PgSqlClient, txLocation: DbTxLocation, event: Pox5EventSetupBond) { + const bond: DbBondInsertValues = { + ...txLocation, + bond_index: parseInt(event.data.bond_index), + target_rate: parseInt(event.data.target_rate), + stx_value_ratio: parseInt(event.data.stx_value_ratio), + min_ustx_ratio: parseInt(event.data.min_ustx_ratio), + early_unlock_bytes: event.data.early_unlock_bytes, + early_unlock_admin: event.data.early_unlock_admin, + first_reward_cycle: parseInt(event.data.first_reward_cycle), + bond_start_height: parseInt(event.data.bond_start_height), + unlock_cycle: parseInt(event.data.unlock_cycle), + unlock_burn_height: parseInt(event.data.unlock_burn_height), + }; + await sql` + INSERT INTO bonds ${sql(bond)} + `; + } + + private async updateBondRegistration( + sql: PgSqlClient, + txLocation: DbTxLocation, + event: Pox5EventRegisterForBond | Pox5EventUpdateBondRegistration + ) { + const bondIndex = parseInt(event.data.bond_index); + const staker = event.data.staker; + const amountUstx = event.data.amount_ustx; + const satsTotal = + event.name === Pox5EventName.RegisterForBond ? event.data.sats_total : event.data.amount_sats; + + if (event.name === Pox5EventName.RegisterForBond) { + const bondRegistration: DbBondRegistrationInsertValues = { + ...txLocation, + bond_index: bondIndex, + signer: event.data.signer, + staker, + amount_ustx: amountUstx, + sats_total: satsTotal, + first_reward_cycle: parseInt(event.data.first_reward_cycle), + unlock_burn_height: parseInt(event.data.unlock_burn_height), + unlock_cycle: parseInt(event.data.unlock_cycle), + is_l1_lock: event.data.is_l1_lock, + }; + await sql` + INSERT INTO bond_registrations ${sql(bondRegistration)} + `; + } else { + const updateResult = await sql` + UPDATE bond_registrations SET + signer = ${event.data.signer}, + sats_total = ${satsTotal}, + amount_ustx = ${amountUstx} + WHERE bond_index = ${bondIndex} + AND staker = ${staker} + AND canonical = true + AND microblock_canonical = true + `; + if (updateResult.count === 0) { + logger.warn( + `Bond registration not found for bond index ${event.data.bond_index} and staker ${event.data.staker}` + ); + } + } + } + + private async updatePrincipalBondPosition( + sql: PgSqlClient, + txLocation: DbTxLocation, + event: + | Pox5EventRegisterForBond + | Pox5EventUpdateBondRegistration + | Pox5EventAnnounceL1EarlyExit + | Pox5EventUnstakeSbtc + ) { + switch (event.name) { + case Pox5EventName.RegisterForBond: + case Pox5EventName.UpdateBondRegistration: { + const bondIndex = parseInt(event.data.bond_index); + const position: DbPrincipalBondPositionInsertValues = { + ...txLocation, + principal: event.data.staker, + bond_index: bondIndex, + status: DbPrincipalBondPositionStatus.Enrolled, + active: true, + btc_locked: + event.name === Pox5EventName.RegisterForBond + ? event.data.sats_total + : event.data.amount_sats, + stx_locked: event.data.amount_ustx, + btc_paid_out: '0', + }; + await sql` + WITH existing_position AS ( + SELECT btc_locked, stx_locked + FROM principal_bond_positions + WHERE principal = ${position.principal} + AND bond_index = ${bondIndex} + AND canonical = true + AND microblock_canonical = true + ), + upserted_position AS ( + INSERT INTO principal_bond_positions ${sql(position)} + ON CONFLICT (principal, bond_index) DO UPDATE SET + tx_id = EXCLUDED.tx_id, + tx_index = EXCLUDED.tx_index, + block_height = EXCLUDED.block_height, + index_block_hash = EXCLUDED.index_block_hash, + parent_index_block_hash = EXCLUDED.parent_index_block_hash, + microblock_hash = EXCLUDED.microblock_hash, + microblock_sequence = EXCLUDED.microblock_sequence, + microblock_canonical = EXCLUDED.microblock_canonical, + canonical = EXCLUDED.canonical, + status = EXCLUDED.status, + active = EXCLUDED.active, + btc_locked = EXCLUDED.btc_locked, + stx_locked = EXCLUDED.stx_locked + RETURNING btc_locked, stx_locked + ), + delta AS ( + SELECT + upserted_position.btc_locked::numeric + - COALESCE(existing_position.btc_locked::numeric, 0) AS btc_delta, + upserted_position.stx_locked::numeric + - COALESCE(existing_position.stx_locked::numeric, 0) AS stx_delta + FROM upserted_position + LEFT JOIN existing_position ON true + ) + UPDATE bonds + SET + btc_locked = bonds.btc_locked + delta.btc_delta, + stx_locked = bonds.stx_locked + delta.stx_delta + FROM delta + WHERE bonds.bond_index = ${bondIndex} + AND bonds.canonical = true + AND bonds.microblock_canonical = true + `; + break; + } + case Pox5EventName.AnnounceL1EarlyExit: + await sql` + WITH updated_position AS ( + UPDATE principal_bond_positions + SET + status = ${DbPrincipalBondPositionStatus.EarlyExit}, + active = false + WHERE principal = ${event.data.staker} + AND bond_index = ${parseInt(event.data.bond_index)} + AND canonical = true + AND microblock_canonical = true + RETURNING + bond_index, + btc_locked::numeric AS previous_btc_locked, + stx_locked::numeric AS previous_stx_locked, + btc_locked::numeric AS new_btc_locked, + stx_locked::numeric AS new_stx_locked + ), + delta AS ( + SELECT + SUM(new_btc_locked - previous_btc_locked) AS btc_delta, + SUM(new_stx_locked - previous_stx_locked) AS stx_delta, + bond_index + FROM updated_position + GROUP BY bond_index + ) + UPDATE bonds + SET + btc_locked = bonds.btc_locked + delta.btc_delta, + stx_locked = bonds.stx_locked + delta.stx_delta + FROM delta + WHERE bonds.bond_index = delta.bond_index + AND bonds.canonical = true + AND bonds.microblock_canonical = true + `; + return; + case Pox5EventName.UnstakeSbtc: + await sql` + WITH existing_position AS ( + SELECT bond_index, btc_locked::numeric AS btc_locked, stx_locked::numeric AS stx_locked + FROM principal_bond_positions + WHERE principal = ${event.data.staker} + AND bond_index = ${parseInt(event.data.bond_index)} + AND canonical = true + AND microblock_canonical = true + ), + updated_position AS ( + UPDATE principal_bond_positions + SET + ${event.data.new_amount_sats == '0' ? sql`status = ${DbPrincipalBondPositionStatus.EarlyExit},` : sql``} + btc_locked = ${event.data.new_amount_sats} + WHERE principal = ${event.data.staker} + AND bond_index = ${parseInt(event.data.bond_index)} + AND canonical = true + AND microblock_canonical = true + RETURNING + bond_index, + btc_locked::numeric AS new_btc_locked, + stx_locked::numeric AS new_stx_locked + ), + delta AS ( + SELECT + updated_position.new_btc_locked - existing_position.btc_locked AS btc_delta, + updated_position.new_stx_locked - existing_position.stx_locked AS stx_delta, + updated_position.bond_index + FROM updated_position + INNER JOIN existing_position USING (bond_index) + ) + UPDATE bonds + SET + btc_locked = bonds.btc_locked + delta.btc_delta, + stx_locked = bonds.stx_locked + delta.stx_delta + FROM delta + WHERE bonds.bond_index = delta.bond_index + AND bonds.canonical = true + AND bonds.microblock_canonical = true + `; + return; + } + } + + private async updateBondRewardDistribution( + sql: PgSqlClient, + txLocation: DbTxLocation, + event: Pox5EventCalculateRewards + ) { + const rewardDistributions: DbBondRewardDistributionInsertValues[] = []; + for (const bondIndex of event.data.bond_periods) { + // TODO: Divide rewards by bond period + rewardDistributions.push({ + ...txLocation, + bond_index: parseInt(bondIndex), + remaining_rewards: event.data.remaining_rewards, + accrued_rewards: event.data.accrued_rewards, + new_reserve: event.data.new_reserve ?? '0', + stx_staker_rewards: event.data.stx_staker_rewards, + stx_cycle: parseInt(event.data.stx_cycle), + cycle_staked_ustx: event.data.cycle_staked_ustx, + next_rewards_per_ustx: event.data.next_rewards_per_ustx, + }); + } + for (const batch of batchIterate(rewardDistributions, INSERT_BATCH_SIZE)) { + await sql` + INSERT INTO bond_reward_distributions ${sql(batch)} + `; + } + } + + private async updateBondAllowlistEntry( + sql: PgSqlClient, + txLocation: DbTxLocation, + event: Pox5EventAddToAllowlist + ) { + const bondIndex = parseInt(event.data.bond_index); + const maxSats = event.data.max_sats; + const bondAllowlistEntry: DbBondAllowlistEntryInsertValues = { + ...txLocation, + bond_index: bondIndex, + staker: event.data.staker, + max_sats: maxSats, + }; + await sql` + WITH inserted AS ( + INSERT INTO bond_allowlist_entries ${sql(bondAllowlistEntry)} + RETURNING bond_index, max_sats + ) + UPDATE bonds AS b + SET btc_capacity = b.btc_capacity + i.max_sats::numeric + FROM inserted AS i + WHERE b.bond_index = i.bond_index + AND b.canonical = true + AND b.microblock_canonical = true + `; + } + private async updatePoxStateUnlockHeight(sql: PgSqlClient, data: DataStoreBlockUpdateData) { if (data.pox_v1_unlock_height !== undefined) { // update the pox_state.pox_v1_unlock_height singleton @@ -526,6 +891,14 @@ export class PgWriteStore extends PgStore { WHERE pox_v3_unlock_height != ${data.pox_v3_unlock_height} `; } + if (data.pox_v4_unlock_height !== undefined) { + // update the pox_state.pox_v4_unlock_height singleton + await sql` + UPDATE pox_state + SET pox_v4_unlock_height = ${data.pox_v4_unlock_height} + WHERE pox_v4_unlock_height != ${data.pox_v4_unlock_height} + `; + } } async updateMinerRewards(sql: PgSqlClient, minerRewards: DbMinerReward[]): Promise { @@ -763,6 +1136,7 @@ export class PgWriteStore extends PgStore { pox2Events: entry.pox2Events.map(e => ({ ...e, block_height: blockHeight })), pox3Events: entry.pox3Events.map(e => ({ ...e, block_height: blockHeight })), pox4Events: entry.pox4Events.map(e => ({ ...e, block_height: blockHeight })), + pox5Events: entry.pox5Events.map(e => ({ ...e, block_height: blockHeight })), }); deployedSmartContracts.push(...entry.smartContracts); contractLogEvents.push(...entry.contractLogEvents); @@ -842,7 +1216,13 @@ export class PgWriteStore extends PgStore { currentMicroblockTip.microblock_sequence === 0 ? sql`tx_count + ${data.txs.length}` : sql`tx_count_unanchored + ${data.txs.length}` - } + }, + bond_count = ( + SELECT COUNT(*)::int + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + ) `; }); @@ -908,20 +1288,20 @@ export class PgWriteStore extends PgStore { logger.info('Updated block zero boot data', tablesUpdates); } - async updatePoxSyntheticEvents< - T extends PoxSyntheticEventTable, + async updatePox4SyntheticEvents< + T extends Pox4SyntheticEventTable, Entry extends { tx: DbTx } & ('pox2_events' extends T - ? { pox2Events: DbPoxSyntheticEvent[] } + ? { pox2Events: DbPox4SyntheticEvent[] } : 'pox3_events' extends T - ? { pox3Events: DbPoxSyntheticEvent[] } + ? { pox3Events: DbPox4SyntheticEvent[] } : 'pox4_events' extends T - ? { pox4Events: DbPoxSyntheticEvent[] } + ? { pox4Events: DbPox4SyntheticEvent[] } : never), >(sql: PgSqlClient, poxTable: T, entries: Entry[]) { - const values: PoxSyntheticEventInsertValues[] = []; + const values: Pox4SyntheticEventInsertValues[] = []; for (const entry of entries) { // eslint-disable-next-line no-useless-assignment - let events: DbPoxSyntheticEvent[] | null = null; + let events: DbPox4SyntheticEvent[] | null = null; switch (poxTable) { case 'pox2_events': assert('pox2Events' in entry); @@ -940,7 +1320,8 @@ export class PgWriteStore extends PgStore { } const tx = entry.tx; for (const event of events ?? []) { - const value: PoxSyntheticEventInsertValues = { + assert(event.pox_version === 'pox4', 'only pox4 events are supported'); + const value: Pox4SyntheticEventInsertValues = { event_index: event.event_index, tx_id: event.tx_id, tx_index: event.tx_index, @@ -980,12 +1361,12 @@ export class PgWriteStore extends PgStore { // Set event-specific columns switch (event.name) { - case SyntheticPoxEventName.HandleUnlock: { + case Pox4EventName.HandleUnlock: { value.first_cycle_locked = event.data.first_cycle_locked.toString(); value.first_unlocked_cycle = event.data.first_unlocked_cycle.toString(); break; } - case SyntheticPoxEventName.StackStx: { + case Pox4EventName.StackStx: { value.lock_period = event.data.lock_period.toString(); value.lock_amount = event.data.lock_amount.toString(); value.start_burn_height = event.data.start_burn_height.toString(); @@ -997,7 +1378,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.StackIncrease: { + case Pox4EventName.StackIncrease: { value.increase_by = event.data.increase_by.toString(); value.total_locked = event.data.total_locked.toString(); if (poxTable === 'pox4_events') { @@ -1007,7 +1388,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.StackExtend: { + case Pox4EventName.StackExtend: { value.extend_count = event.data.extend_count.toString(); value.unlock_burn_height = event.data.unlock_burn_height.toString(); if (poxTable === 'pox4_events') { @@ -1017,7 +1398,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.DelegateStx: { + case Pox4EventName.DelegateStx: { value.amount_ustx = event.data.amount_ustx.toString(); value.delegate_to = event.data.delegate_to; value.unlock_burn_height = event.data.unlock_burn_height?.toString() ?? null; @@ -1027,7 +1408,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.DelegateStackStx: { + case Pox4EventName.DelegateStackStx: { value.lock_period = event.data.lock_period.toString(); value.lock_amount = event.data.lock_amount.toString(); value.start_burn_height = event.data.start_burn_height.toString(); @@ -1039,7 +1420,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.DelegateStackIncrease: { + case Pox4EventName.DelegateStackIncrease: { value.increase_by = event.data.increase_by.toString(); value.total_locked = event.data.total_locked.toString(); value.delegator = event.data.delegator; @@ -1049,7 +1430,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.DelegateStackExtend: { + case Pox4EventName.DelegateStackExtend: { value.extend_count = event.data.extend_count.toString(); value.unlock_burn_height = event.data.unlock_burn_height.toString(); value.delegator = event.data.delegator; @@ -1059,7 +1440,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.StackAggregationCommit: { + case Pox4EventName.StackAggregationCommit: { value.reward_cycle = event.data.reward_cycle.toString(); value.amount_ustx = event.data.amount_ustx.toString(); if (poxTable === 'pox4_events') { @@ -1069,7 +1450,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.StackAggregationCommitIndexed: { + case Pox4EventName.StackAggregationCommitIndexed: { value.reward_cycle = event.data.reward_cycle.toString(); value.amount_ustx = event.data.amount_ustx.toString(); if (poxTable === 'pox4_events') { @@ -1079,7 +1460,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.StackAggregationIncrease: { + case Pox4EventName.StackAggregationIncrease: { value.reward_cycle = event.data.reward_cycle.toString(); value.amount_ustx = event.data.amount_ustx.toString(); if (poxTable === 'pox4_events') { @@ -1088,7 +1469,7 @@ export class PgWriteStore extends PgStore { } break; } - case SyntheticPoxEventName.RevokeDelegateStx: { + case Pox4EventName.RevokeDelegateStx: { value.delegate_to = event.data.delegate_to; if (poxTable === 'pox4_events') { value.end_cycle_id = event.data.end_cycle_id?.toString() ?? null; @@ -1098,7 +1479,7 @@ export class PgWriteStore extends PgStore { } default: { throw new Error( - `Unexpected Pox synthetic event name: ${(event as DbPoxSyntheticEvent).name}` + `Unexpected Pox synthetic event name: ${(event as DbPox4SyntheticEvent).name}` ); } } @@ -2923,9 +3304,9 @@ export class PgWriteStore extends PgStore { q.enqueue(() => this.updateStxEvents(sql, txs)); q.enqueue(() => this.updatePrincipalTxs(sql, txs)); q.enqueue(() => this.updateSmartContractEvents(sql, txs)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox2_events', txs)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox3_events', txs)); - q.enqueue(() => this.updatePoxSyntheticEvents(sql, 'pox4_events', txs)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox2_events', txs)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox3_events', txs)); + q.enqueue(() => this.updatePox4SyntheticEvents(sql, 'pox4_events', txs)); q.enqueue(() => this.updateStxLockEvents(sql, txs)); q.enqueue(() => this.updateFtEvents(sql, txs)); for (const entry of txs) { @@ -4000,7 +4381,13 @@ export class PgWriteStore extends PgStore { await sql` UPDATE chain_tip SET tx_count = tx_count + ${txCountDelta}, - tx_count_unanchored = tx_count_unanchored + ${txCountDelta} + tx_count_unanchored = tx_count_unanchored + ${txCountDelta}, + bond_count = ( + SELECT COUNT(*)::int + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + ) `; } return updatedEntities; diff --git a/src/datastore/v3/constants.ts b/src/datastore/v3/constants.ts index 3dc7d489f2..b0b49a7157 100644 --- a/src/datastore/v3/constants.ts +++ b/src/datastore/v3/constants.ts @@ -87,3 +87,57 @@ export const MEMPOOL_TX_COLUMNS = [ 'tenure_change_previous_tenure_blocks', 'tenure_change_pubkey_hash', ]; + +export const BOND_SUMMARY_COLUMNS = [ + 'bond_index', + 'target_rate', + 'stx_value_ratio', + 'min_ustx_ratio', + 'first_reward_cycle', + 'bond_start_height', + 'unlock_cycle', + 'unlock_burn_height', + 'btc_capacity', + 'btc_locked', + 'stx_locked', + 'btc_paid_out', + 'allowed_count', + 'registered_count', +]; + +export const BOND_COLUMNS = [ + ...BOND_SUMMARY_COLUMNS, + 'tx_id', + 'block_height', + 'block_hash', + 'index_block_hash', + 'block_time', + 'tx_index', + 'burn_block_height', + 'burn_block_time', + 'early_unlock_bytes', + 'early_unlock_admin', +]; + +export const BOND_ALLOWLIST_ENTRY_COLUMNS = [ + 'staker', + 'max_sats', + 'block_height', + 'microblock_sequence', + 'tx_index', +]; + +export const BOND_REGISTRATION_COLUMNS = [ + 'bond_index', + 'signer', + 'staker', + 'amount_ustx', + 'sats_total', + 'first_reward_cycle', + 'unlock_burn_height', + 'unlock_cycle', + 'is_l1_lock', + 'block_height', + 'microblock_sequence', + 'tx_index', +]; diff --git a/src/datastore/v3/helpers.ts b/src/datastore/v3/helpers.ts new file mode 100644 index 0000000000..faff452d0d --- /dev/null +++ b/src/datastore/v3/helpers.ts @@ -0,0 +1,42 @@ +import { TransactionCursor } from '../../api/schemas/v3/cursors.js'; +import { I32_MAX } from '../../helpers.js'; + +const MAX_TX_INDEX = 0x7fff; + +export type TransactionCursorRow = { + block_height: number; + microblock_sequence: number; + tx_index: number; +}; + +const parseTransactionCursor = (cursor: TransactionCursor): TransactionCursorRow => { + const [blockHeightStr, microblockSequenceStr, txIndexStr] = cursor.split(':'); + return { + block_height: parseInt(blockHeightStr, 10), + microblock_sequence: parseInt(microblockSequenceStr, 10), + tx_index: parseInt(txIndexStr, 10), + }; +}; + +/** + * Resolves a transaction cursor to a transaction cursor row. + * @param cursor - The transaction cursor. + * @param exactCursorExists - A function that checks if a cursor exists. + * @returns The transaction cursor row. + */ +export const resolveTransactionCursor = async ( + cursor: TransactionCursor, + exactCursorExists: (cursor: TransactionCursorRow) => Promise +): Promise => { + const parsed = parseTransactionCursor(cursor); + if (parsed.microblock_sequence !== 0 || parsed.tx_index !== 0) { + return parsed; + } + if (await exactCursorExists(parsed)) { + return parsed; + } + return { ...parsed, microblock_sequence: I32_MAX, tx_index: MAX_TX_INDEX }; +}; + +export const encodeTransactionCursor = (tx: TransactionCursorRow): TransactionCursor => + `${tx.block_height}:${tx.microblock_sequence}:${tx.tx_index}`; diff --git a/src/datastore/v3/pg-store-v3.ts b/src/datastore/v3/pg-store-v3.ts index 5a503cdf50..3029a46a0d 100644 --- a/src/datastore/v3/pg-store-v3.ts +++ b/src/datastore/v3/pg-store-v3.ts @@ -1,13 +1,23 @@ import { BasePgStoreModule } from '@stacks/api-toolkit'; import { + DbBond, + DbBondAllowlistEntry, + DbBondRegistration, + DbBondSummary, DbCursorPaginatedResult, DbMempoolTransaction, DbMempoolTransactionSummary, DbPrincipalTransactionSummary, DbTransaction, + DbTransactionCursor, + DbTransactionEvent, DbTransactionSummary, } from './types.js'; import { + BOND_ALLOWLIST_ENTRY_COLUMNS, + BOND_REGISTRATION_COLUMNS, + BOND_COLUMNS, + BOND_SUMMARY_COLUMNS, MEMPOOL_TX_COLUMNS, MEMPOOL_TX_SUMMARY_COLUMNS, TX_COLUMNS, @@ -19,6 +29,13 @@ import { normalizeHashString } from '../../helpers.js'; import { BlockIdParam } from '../../api/routes/v2/schemas.js'; import { InvalidRequestError, InvalidRequestErrorType } from '../../errors.js'; import { TransactionIncludeField } from '../../api/schemas/v3/entities/transactions.js'; +import type { + BondCursor, + TransactionCursor, + TransactionEventCursor, +} from '../../api/schemas/v3/cursors.js'; +import { encodeTransactionCursor, resolveTransactionCursor } from './helpers.js'; +import { DbEventTypeId } from '../common.js'; export class PgStoreV3 extends BasePgStoreModule { /** @@ -28,19 +45,27 @@ export class PgStoreV3 extends BasePgStoreModule { */ async getTransactionSummaries(args: { limit: number; - cursor?: string; + cursor?: TransactionCursor; }): Promise> { return await this.sqlTransaction(async sql => { let cursorFilter = sql``; if (args.cursor) { - const parts = args.cursor.split(':'); - const [blockHeightStr, microblockSequenceStr, txIndexStr] = parts; - const blockHeight = parseInt(blockHeightStr, 10); - const microblockSequence = parseInt(microblockSequenceStr, 10); - const txIndex = parseInt(txIndexStr, 10); + const cursor = await resolveTransactionCursor(args.cursor, async cursor => { + const exactCursorQuery = await sql<{ exists: boolean }[]>` + SELECT EXISTS ( + SELECT 1 + FROM txs + WHERE canonical = true + AND microblock_canonical = true + AND (block_height, microblock_sequence, tx_index) + = (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + ) AS exists + `; + return exactCursorQuery[0]?.exists ?? false; + }); cursorFilter = sql` AND (block_height, microblock_sequence, tx_index) - <= (${blockHeight}, ${microblockSequence}, ${txIndex}) + <= (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) `; } const resultQuery = await sql< @@ -62,15 +87,10 @@ export class PgStoreV3 extends BasePgStoreModule { const total = resultQuery.count > 0 ? resultQuery[0].total : 0; const nextResult = resultQuery[resultQuery.length - 1]; - const nextCursor = - hasNextPage && nextResult - ? `${nextResult.block_height}:${nextResult.microblock_sequence}:${nextResult.tx_index}` - : null; + const nextCursor = hasNextPage && nextResult ? encodeTransactionCursor(nextResult) : null; const firstResult = results[0]; - const currentCursor = firstResult - ? `${firstResult.block_height}:${firstResult.microblock_sequence}:${firstResult.tx_index}` - : null; + const currentCursor = firstResult ? encodeTransactionCursor(firstResult) : null; let prevCursor: string | null = null; if (firstResult) { @@ -88,12 +108,11 @@ export class PgStoreV3 extends BasePgStoreModule { ${firstResult.tx_index} ) ORDER BY block_height ASC, microblock_sequence ASC, tx_index ASC - OFFSET ${args.limit - 1} - LIMIT 1 + LIMIT ${args.limit} `; if (prevPageQuery.length > 0) { - const prevPage = prevPageQuery[0]; - prevCursor = `${prevPage.block_height}:${prevPage.microblock_sequence}:${prevPage.tx_index}`; + const prevPage = prevPageQuery[prevPageQuery.length - 1]; + prevCursor = encodeTransactionCursor(prevPage); } } @@ -117,19 +136,28 @@ export class PgStoreV3 extends BasePgStoreModule { async getPrincipalTransactionSummaries(args: { principal: Principal; limit: number; - cursor?: string; + cursor?: TransactionCursor; }): Promise> { return await this.sqlTransaction(async sql => { let cursorFilter = sql``; if (args.cursor) { - const parts = args.cursor.split(':'); - const [blockHeightStr, microblockSequenceStr, txIndexStr] = parts; - const blockHeight = parseInt(blockHeightStr, 10); - const microblockSequence = parseInt(microblockSequenceStr, 10); - const txIndex = parseInt(txIndexStr, 10); + const cursor = await resolveTransactionCursor(args.cursor, async cursor => { + const exactCursorQuery = await sql<{ exists: boolean }[]>` + SELECT EXISTS ( + SELECT 1 + FROM principal_txs + WHERE canonical = true + AND microblock_canonical = true + AND principal = ${args.principal} + AND (block_height, microblock_sequence, tx_index) + = (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + ) AS exists + `; + return exactCursorQuery[0]?.exists ?? false; + }); cursorFilter = sql` AND (block_height, microblock_sequence, tx_index) - <= (${blockHeight}, ${microblockSequence}, ${txIndex}) + <= (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) `; } const resultQuery = await sql< @@ -182,15 +210,10 @@ export class PgStoreV3 extends BasePgStoreModule { const total = resultQuery.count > 0 ? resultQuery[0].total : 0; const nextResult = resultQuery[resultQuery.length - 1]; - const nextCursor = - hasNextPage && nextResult - ? `${nextResult.block_height}:${nextResult.microblock_sequence}:${nextResult.tx_index}` - : null; + const nextCursor = hasNextPage && nextResult ? encodeTransactionCursor(nextResult) : null; const firstResult = results[0]; - const currentCursor = firstResult - ? `${firstResult.block_height}:${firstResult.microblock_sequence}:${firstResult.tx_index}` - : null; + const currentCursor = firstResult ? encodeTransactionCursor(firstResult) : null; let prevCursor: string | null = null; if (firstResult) { @@ -209,12 +232,11 @@ export class PgStoreV3 extends BasePgStoreModule { ${firstResult.tx_index} ) ORDER BY block_height ASC, microblock_sequence ASC, tx_index ASC - OFFSET ${args.limit - 1} - LIMIT 1 + LIMIT ${args.limit} `; if (prevPageQuery.length > 0) { - const prevPage = prevPageQuery[0]; - prevCursor = `${prevPage.block_height}:${prevPage.microblock_sequence}:${prevPage.tx_index}`; + const prevPage = prevPageQuery[prevPageQuery.length - 1]; + prevCursor = encodeTransactionCursor(prevPage); } } @@ -278,11 +300,12 @@ export class PgStoreV3 extends BasePgStoreModule { WHERE pruned = false AND (receipt_time, tx_id) > (${firstResult.receipt_time}, ${firstResult.tx_id}) ORDER BY receipt_time ASC, tx_id ASC - OFFSET ${args.limit - 1} - LIMIT 1 + LIMIT ${args.limit} `; prevCursor = - prevPageQuery.length > 0 ? encodeMempoolTxSummaryCursor(prevPageQuery[0]) : null; + prevPageQuery.length > 0 + ? encodeMempoolTxSummaryCursor(prevPageQuery[prevPageQuery.length - 1]) + : null; } return { @@ -305,7 +328,7 @@ export class PgStoreV3 extends BasePgStoreModule { async getBlockTransactionSummaries(args: { block: BlockIdParam; limit: number; - cursor?: string; + cursor?: TransactionCursor; }): Promise> { return await this.sqlTransaction(async sql => { const blockFilter = @@ -332,14 +355,23 @@ export class PgStoreV3 extends BasePgStoreModule { let cursorFilter = sql``; if (args.cursor) { - const parts = args.cursor.split(':'); - const [blockHeightStr, microblockSequenceStr, txIndexStr] = parts; - const blockHeight = parseInt(blockHeightStr, 10); - const microblockSequence = parseInt(microblockSequenceStr, 10); - const txIndex = parseInt(txIndexStr, 10); + const cursor = await resolveTransactionCursor(args.cursor, async cursor => { + const exactCursorQuery = await sql<{ exists: boolean }[]>` + SELECT EXISTS ( + SELECT 1 + FROM txs + WHERE canonical = true + AND microblock_canonical = true + AND index_block_hash = ${index_block_hash} + AND (block_height, microblock_sequence, tx_index) + = (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + ) AS exists + `; + return exactCursorQuery[0]?.exists ?? false; + }); cursorFilter = sql` AND (block_height, microblock_sequence, tx_index) - <= (${blockHeight}, ${microblockSequence}, ${txIndex}) + <= (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) `; } @@ -358,15 +390,10 @@ export class PgStoreV3 extends BasePgStoreModule { const results = hasNextPage ? resultQuery.slice(0, args.limit) : resultQuery; const nextResult = resultQuery[resultQuery.length - 1]; - const nextCursor = - hasNextPage && nextResult - ? `${nextResult.block_height}:${nextResult.microblock_sequence}:${nextResult.tx_index}` - : null; + const nextCursor = hasNextPage && nextResult ? encodeTransactionCursor(nextResult) : null; const firstResult = results[0]; - const currentCursor = firstResult - ? `${firstResult.block_height}:${firstResult.microblock_sequence}:${firstResult.tx_index}` - : null; + const currentCursor = firstResult ? encodeTransactionCursor(firstResult) : null; let prevCursor: string | null = null; if (firstResult) { @@ -385,12 +412,11 @@ export class PgStoreV3 extends BasePgStoreModule { ${firstResult.tx_index} ) ORDER BY block_height ASC, microblock_sequence ASC, tx_index ASC - OFFSET ${args.limit - 1} - LIMIT 1 + LIMIT ${args.limit} `; if (prevPageQuery.length > 0) { - const prevPage = prevPageQuery[0]; - prevCursor = `${prevPage.block_height}:${prevPage.microblock_sequence}:${prevPage.tx_index}`; + const prevPage = prevPageQuery[prevPageQuery.length - 1]; + prevCursor = encodeTransactionCursor(prevPage); } } @@ -483,4 +509,474 @@ export class PgStoreV3 extends BasePgStoreModule { return null; }); } + + async getTransactionEvents(args: { + txId: string; + limit: number; + cursor?: TransactionEventCursor; + }): Promise> { + return await this.sqlTransaction(async sql => { + const limit = args.limit; + const txCheck = await sql<{ event_count: number }[]>` + SELECT event_count + FROM txs + WHERE tx_id = ${args.txId} AND canonical = true AND microblock_canonical = true + LIMIT 1 + `; + if (txCheck.count === 0) + throw new InvalidRequestError( + `Transaction not found`, + InvalidRequestErrorType.invalid_param + ); + + let cursorFilter = sql``; + if (args.cursor) { + cursorFilter = sql`AND event_index >= ${parseInt(args.cursor, 10)}`; + } + + const eventCond = sql` + canonical = true AND microblock_canonical = true AND tx_id = ${args.txId} ${cursorFilter} + `; + const resultQuery = await sql` + WITH events AS ( + ( + SELECT + sender, + recipient, + event_index, + amount, + NULL as asset_identifier, + NULL as contract_identifier, + NULL as topic, + NULL::bytea as value, + ${DbEventTypeId.StxAsset}::int as event_type_id, + asset_event_type_id, + memo, + NULL::int as unlock_height + FROM stx_events + WHERE ${eventCond} + ) + UNION ALL + ( + SELECT + sender, + recipient, + event_index, + amount, + asset_identifier, + NULL as contract_identifier, + NULL as topic, + NULL::bytea as value, + ${DbEventTypeId.FungibleTokenAsset}::int as event_type_id, + asset_event_type_id, + NULL::bytea as memo, + NULL::int as unlock_height + FROM ft_events + WHERE ${eventCond} + ) + UNION ALL + ( + SELECT + sender, + recipient, + event_index, + 0 as amount, + asset_identifier, + NULL as contract_identifier, + NULL as topic, + value, + ${DbEventTypeId.NonFungibleTokenAsset}::int as event_type_id, + asset_event_type_id, + NULL::bytea as memo, + NULL::int as unlock_height + FROM nft_events + WHERE ${eventCond} + ) + UNION ALL + ( + SELECT + locked_address as sender, + NULL as recipient, + event_index, + locked_amount as amount, + NULL as asset_identifier, + NULL as contract_identifier, + NULL as topic, + NULL::bytea as value, + ${DbEventTypeId.StxLock}::int as event_type_id, + 0 as asset_event_type_id, + NULL::bytea as memo, + unlock_height + FROM stx_lock_events + WHERE ${eventCond} + ) + UNION ALL + ( + SELECT + NULL as sender, + NULL as recipient, + event_index, + 0 as amount, + NULL as asset_identifier, + contract_identifier, + topic, + value, + ${DbEventTypeId.SmartContractLog}::int as event_type_id, + 0 as asset_event_type_id, + NULL::bytea as memo, + NULL::int as unlock_height + FROM contract_logs + WHERE ${eventCond} + ) + ) + SELECT * + FROM events + ORDER BY event_index ASC + LIMIT ${limit + 1} + `; + const hasNextPage = resultQuery.count > limit; + const results = hasNextPage ? resultQuery.slice(0, limit) : resultQuery; + const firstResult = results[0]; + const extraResult = hasNextPage ? resultQuery[limit] : null; + const prevCursor = + firstResult && firstResult.event_index > 0 + ? Math.max(firstResult.event_index - limit, 0).toString() + : null; + + return { + total: txCheck[0].event_count, + limit, + offset: 0, + next_cursor: extraResult ? extraResult.event_index.toString() : null, + prev_cursor: prevCursor, + current_cursor: firstResult ? firstResult.event_index.toString() : null, + results, + }; + }); + } + + /** + * Gets the summaries for all bonds. + * @param args - The arguments for the query. + * @returns The summaries for all bonds. + */ + async getBondSummaries(args: { + limit: number; + cursor?: BondCursor; + }): Promise & { burn_block_height: number }> { + return await this.sqlTransaction(async sql => { + const limit = args.limit; + const cursorFilter = args.cursor + ? sql`AND bond_index <= ${parseInt(args.cursor, 10)}` + : sql``; + + const totalQuery = await sql<{ total: number }[]>` + SELECT bond_count AS total + FROM chain_tip + `; + + const resultQuery = await sql` + SELECT ${sql(BOND_SUMMARY_COLUMNS)} + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + ${cursorFilter} + ORDER BY bond_index DESC + LIMIT ${limit + 1} + `; + + const hasNextPage = resultQuery.count > limit; + const results = hasNextPage ? resultQuery.slice(0, limit) : resultQuery; + const firstResult = results[0]; + const extraResult = hasNextPage ? resultQuery[limit] : null; + + let prevCursor: string | null = null; + if (firstResult) { + const prevPageQuery = await sql[]>` + SELECT bond_index + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + AND bond_index > ${firstResult.bond_index} + ORDER BY bond_index ASC + LIMIT ${limit} + `; + prevCursor = + prevPageQuery.length > 0 + ? prevPageQuery[prevPageQuery.length - 1].bond_index.toString() + : null; + } + + const chainTip = await sql<{ burn_block_height: number }[]>` + SELECT burn_block_height FROM chain_tip LIMIT 1 + `; + return { + limit, + next_cursor: extraResult ? extraResult.bond_index.toString() : null, + prev_cursor: prevCursor, + current_cursor: firstResult ? firstResult.bond_index.toString() : null, + total: totalQuery[0]?.total ?? 0, + results, + burn_block_height: chainTip[0]?.burn_block_height ?? 0, + }; + }); + } + + /** + * Gets a bond by index. + * @param args - The arguments for the query. + * @returns The bond by index. + */ + async getBond(args: { + bondIndex: number; + }): Promise<(DbBond & { burn_block_height: number }) | null> { + return await this.sqlTransaction(async sql => { + const chainTip = await sql<{ burn_block_height: number }[]>` + SELECT burn_block_height FROM chain_tip LIMIT 1 + `; + const result = await sql` + SELECT ${this.sql(BOND_COLUMNS)} + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + LIMIT 1 + `; + return result[0] + ? { ...result[0], burn_block_height: chainTip[0]?.burn_block_height ?? 0 } + : null; + }); + } + + /** + * Gets the allowlist entries for a bond. + * @param args - The arguments for the query. + * @returns The allowlist entries for a bond. + */ + async getBondAllowlistEntries(args: { + bondIndex: number; + limit: number; + cursor?: TransactionCursor; + }): Promise> { + return await this.sqlTransaction(async sql => { + const limit = args.limit; + let cursorFilter = sql``; + if (args.cursor) { + const cursor = await resolveTransactionCursor(args.cursor, async cursor => { + const exactCursorQuery = await sql<{ exists: boolean }[]>` + SELECT EXISTS ( + SELECT 1 + FROM bond_allowlist_entries + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND (block_height, microblock_sequence, tx_index) + = (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + ) AS exists + `; + return exactCursorQuery[0]?.exists ?? false; + }); + cursorFilter = sql` + AND (block_height, microblock_sequence, tx_index) + <= (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + `; + } + + const totalQuery = await sql<{ total: number }[]>` + SELECT allowed_count AS total + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + LIMIT 1 + `; + + const resultQuery = await sql<(DbBondAllowlistEntry & DbTransactionCursor)[]>` + SELECT ${sql(BOND_ALLOWLIST_ENTRY_COLUMNS)} + FROM bond_allowlist_entries + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + ${cursorFilter} + ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC + LIMIT ${limit + 1} + `; + + const hasNextPage = resultQuery.count > limit; + const results = hasNextPage ? resultQuery.slice(0, limit) : resultQuery; + const firstResult = results[0]; + const extraResult = hasNextPage ? resultQuery[limit] : null; + + let prevCursor: string | null = null; + if (firstResult) { + const prevPageQuery = await sql` + SELECT block_height, microblock_sequence, tx_index + FROM bond_allowlist_entries + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND (block_height, microblock_sequence, tx_index) + > ( + ${firstResult.block_height}, + ${firstResult.microblock_sequence}, + ${firstResult.tx_index} + ) + ORDER BY block_height ASC, microblock_sequence ASC, tx_index ASC + LIMIT ${limit} + `; + prevCursor = + prevPageQuery.length > 0 + ? encodeTransactionCursor(prevPageQuery[prevPageQuery.length - 1]) + : null; + } + + return { + limit, + next_cursor: extraResult ? encodeTransactionCursor(extraResult) : null, + prev_cursor: prevCursor, + current_cursor: firstResult ? encodeTransactionCursor(firstResult) : null, + total: totalQuery[0]?.total ?? 0, + results, + }; + }); + } + + /** + * Gets an allowlist entry for a bond and principal. + * @param args - The arguments for the query. + * @returns The allowlist entry for a bond and principal. + */ + async getBondAllowlistEntry(args: { + bondIndex: number; + principal: Principal; + }): Promise { + return await this.sqlTransaction(async sql => { + const result = await sql` + SELECT staker, max_sats + FROM bond_allowlist_entries + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND staker = ${args.principal} + LIMIT 1 + `; + return result[0] ?? null; + }); + } + + /** + * Gets the registrations for a bond. + * @param args - The arguments for the query. + * @returns The registrations for a bond. + */ + async getBondRegistrations(args: { + bondIndex: number; + limit: number; + cursor?: TransactionCursor; + }): Promise> { + return await this.sqlTransaction(async sql => { + const limit = args.limit; + let cursorFilter = sql``; + if (args.cursor) { + const cursor = await resolveTransactionCursor(args.cursor, async cursor => { + const exactCursorQuery = await sql<{ exists: boolean }[]>` + SELECT EXISTS ( + SELECT 1 + FROM bond_registrations + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND (block_height, microblock_sequence, tx_index) + = (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + ) AS exists + `; + return exactCursorQuery[0]?.exists ?? false; + }); + cursorFilter = sql` + AND (block_height, microblock_sequence, tx_index) + <= (${cursor.block_height}, ${cursor.microblock_sequence}, ${cursor.tx_index}) + `; + } + + const totalQuery = await sql<{ total: number }[]>` + SELECT registered_count AS total + FROM bonds + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + LIMIT 1 + `; + + const resultQuery = await sql<(DbBondRegistration & DbTransactionCursor)[]>` + SELECT ${sql(BOND_REGISTRATION_COLUMNS)} + FROM bond_registrations + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + ${cursorFilter} + ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC + LIMIT ${limit + 1} + `; + + const hasNextPage = resultQuery.count > limit; + const results = hasNextPage ? resultQuery.slice(0, limit) : resultQuery; + const firstResult = results[0]; + const extraResult = hasNextPage ? resultQuery[limit] : null; + + let prevCursor: string | null = null; + if (firstResult) { + const prevPageQuery = await sql` + SELECT block_height, microblock_sequence, tx_index + FROM bond_registrations + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND (block_height, microblock_sequence, tx_index) + > ( + ${firstResult.block_height}, + ${firstResult.microblock_sequence}, + ${firstResult.tx_index} + ) + ORDER BY block_height ASC, microblock_sequence ASC, tx_index ASC + LIMIT ${limit} + `; + prevCursor = + prevPageQuery.length > 0 + ? encodeTransactionCursor(prevPageQuery[prevPageQuery.length - 1]) + : null; + } + + return { + limit, + next_cursor: extraResult ? encodeTransactionCursor(extraResult) : null, + prev_cursor: prevCursor, + current_cursor: firstResult ? encodeTransactionCursor(firstResult) : null, + total: totalQuery[0]?.total ?? 0, + results, + }; + }); + } + + /** + * Gets a registration for a bond and principal. + * @param args - The arguments for the query. + * @returns The latest registration for a bond and principal. + */ + async getBondRegistration(args: { + bondIndex: number; + principal: Principal; + }): Promise { + return await this.sqlTransaction(async sql => { + const result = await sql` + SELECT ${sql(BOND_REGISTRATION_COLUMNS)} + FROM bond_registrations + WHERE canonical = true + AND microblock_canonical = true + AND bond_index = ${args.bondIndex} + AND staker = ${args.principal} + LIMIT 1 + `; + return result[0] ?? null; + }); + } } diff --git a/src/datastore/v3/types.ts b/src/datastore/v3/types.ts index 818e9b8f56..434358aad6 100644 --- a/src/datastore/v3/types.ts +++ b/src/datastore/v3/types.ts @@ -1,4 +1,4 @@ -import { DbTxStatus, DbTxTypeId } from '../common.js'; +import { DbAssetEventTypeId, DbEventTypeId, DbTxStatus, DbTxTypeId } from '../common.js'; export type DbCursorPaginatedResult = { limit: number; @@ -108,3 +108,71 @@ export interface DbMempoolTransaction extends DbMempoolTransactionSummary { tenure_change_previous_tenure_blocks: number | null; tenure_change_pubkey_hash: string | null; } + +export interface DbTransactionEvent { + event_index: number; + amount: string; + event_type_id: DbEventTypeId; + asset_event_type_id: DbAssetEventTypeId; + sender: string | null; + recipient: string | null; + asset_identifier: string | null; + contract_identifier: string | null; + topic: string | null; + value: string | null; + memo: string | null; + unlock_height: number | null; +} + +export interface DbBondSummary { + bond_index: number; + target_rate: number; + stx_value_ratio: number; + min_ustx_ratio: number; + first_reward_cycle: number; + bond_start_height: number; + unlock_cycle: number; + unlock_burn_height: number; + btc_capacity: string; + btc_locked: string; + stx_locked: string; + btc_paid_out: string; + allowed_count: number; + registered_count: number; +} + +export interface DbBond extends DbBondSummary { + tx_id: string; + block_height: number; + block_hash: string; + index_block_hash: string; + block_time: number; + tx_index: number; + burn_block_height: number; + burn_block_time: number; + early_unlock_bytes: string; + early_unlock_admin: string; +} + +export interface DbBondAllowlistEntry { + staker: string; + max_sats: string; +} + +export interface DbBondRegistration { + bond_index: number; + signer: string; + staker: string; + amount_ustx: string; + sats_total: string; + first_reward_cycle: number; + unlock_burn_height: number; + unlock_cycle: number; + is_l1_lock: boolean; +} + +export interface DbTransactionCursor { + block_height: number; + microblock_sequence: number; + tx_index: number; +} diff --git a/src/event-stream/event-server.ts b/src/event-stream/event-server.ts index 3580269a60..f6d5766d0f 100644 --- a/src/event-stream/event-server.ts +++ b/src/event-stream/event-server.ts @@ -22,10 +22,11 @@ import { DataStoreTxEventData, DbMicroblock, DataStoreAttachmentData, - DbPoxSyntheticEvent, + DbPox4SyntheticEvent, DbTxStatus, DbPoxSetSigners, DbBurnBlockPoxTx, + DbPox5SyntheticEvent, } from '../datastore/common.js'; import { getTxSenderAddress, @@ -36,7 +37,12 @@ import { isPoxPrintEvent, newCoreNoreBlockEventCounts, } from './reader.js'; -import { decodeClarityValue, decodeTransaction, TxPayloadTypeID } from '@stacks/codec'; +import { + decodeClarityValue, + decodePoxSyntheticEvent, + decodeTransaction, + TxPayloadTypeID, +} from '@stacks/codec'; import type { ClarityValueBuffer, ClarityValueStringAscii, ClarityValueTuple } from '@stacks/codec'; import { BnsContractIdentifier } from './bns/bns-constants.js'; import { @@ -51,10 +57,9 @@ import { getTxDbStatus, } from '../datastore/helpers.js'; import { handleBnsImport } from '../import-v1/index.js'; -import { decodePoxSyntheticPrintEvent } from './pox-event-parsing.js'; import { hexToBuffer, isProdEnv, logger, PINO_LOGGER_CONFIG, stopwatch } from '@stacks/api-toolkit'; import { ENV } from '../env.js'; -import { POX_2_CONTRACT_NAME, POX_3_CONTRACT_NAME, POX_4_CONTRACT_NAME } from '../pox-helpers.js'; +import { POX_2_CONTRACT_NAME, POX_3_CONTRACT_NAME, POX_4_CONTRACT_NAME } from './pox-constants.js'; import { DropMempoolTxMessage, NewBlockEvent, @@ -277,6 +282,7 @@ function parseDataStoreTxEventData( pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }; switch (tx.parsed_tx.payload.type_id) { case TxPayloadTypeID.VersionedSmartContract: @@ -313,7 +319,8 @@ function parseDataStoreTxEventData( return dbTx; }); - const poxEventLogs: Map = new Map(); + const poxEventLogs: Map = + new Map(); for (const event of events) { if (!event.committed) { @@ -364,39 +371,42 @@ function parseDataStoreTxEventData( dbTx.contractLogEvents.push(entry); if (isPoxPrintEvent(event)) { - const network = getChainIDNetwork(chainId) === 'mainnet' ? 'mainnet' : 'testnet'; + const network = getChainIDNetwork(chainId); const [, contractName] = event.contract_event.contract_identifier.split('.'); - // pox-1 is handled in custom node events - const processSyntheticEvent = [ - POX_2_CONTRACT_NAME, - POX_3_CONTRACT_NAME, - POX_4_CONTRACT_NAME, - ].includes(contractName); - if (processSyntheticEvent) { - const poxEventData = decodePoxSyntheticPrintEvent( - event.contract_event.raw_value, - network - ); - if (poxEventData !== null) { - logger.debug(`Synthetic pox event data for ${contractName}:`, poxEventData); - const dbPoxEvent: DbPoxSyntheticEvent = { - ...dbEvent, - ...poxEventData, - }; - poxEventLogs.set(dbPoxEvent, entry); - switch (contractName) { - case POX_2_CONTRACT_NAME: { - dbTx.pox2Events.push(dbPoxEvent); - break; - } - case POX_3_CONTRACT_NAME: { - dbTx.pox3Events.push(dbPoxEvent); - break; - } - case POX_4_CONTRACT_NAME: { - dbTx.pox4Events.push(dbPoxEvent); - break; + const poxEvent = decodePoxSyntheticEvent(event.contract_event.raw_value, network); + if (poxEvent) { + logger.debug(`Decoded pox event for ${contractName}:`, poxEvent); + switch (poxEvent.pox_version) { + case 'pox4': { + const dbPoxEvent: DbPox4SyntheticEvent = { + ...dbEvent, + ...poxEvent, + }; + poxEventLogs.set(dbPoxEvent, entry); + switch (contractName) { + case POX_2_CONTRACT_NAME: { + dbTx.pox2Events.push(dbPoxEvent); + break; + } + case POX_3_CONTRACT_NAME: { + dbTx.pox3Events.push(dbPoxEvent); + break; + } + case POX_4_CONTRACT_NAME: { + dbTx.pox4Events.push(dbPoxEvent); + break; + } } + break; + } + case 'pox5': { + const dbPoxEvent: DbPox5SyntheticEvent = { + ...dbEvent, + ...poxEvent, + }; + dbTx.pox5Events.push(dbPoxEvent); + poxEventLogs.set(dbPoxEvent, entry); + break; } } } @@ -566,7 +576,7 @@ function parseDataStoreTxEventData( for (let i = 0; i < sortedEvents.length; i++) { sortedEvents[i].event_index = i; } - for (const poxEvent of [tx.pox2Events, tx.pox3Events, tx.pox4Events].flat()) { + for (const poxEvent of [tx.pox2Events, tx.pox3Events, tx.pox4Events, tx.pox5Events].flat()) { const associatedLogEvent = poxEventLogs.get(poxEvent); if (!associatedLogEvent) { throw new Error(`Missing associated contract log event for pox event ${poxEvent.tx_id}`); @@ -1184,7 +1194,9 @@ export function parseNewBlockMessage( } poxSetSigners = { cycle_number: msg.cycle_number, - pox_ustx_threshold: BigInt(msg.reward_set.pox_ustx_threshold), + pox_ustx_threshold: msg.reward_set.pox_ustx_threshold + ? BigInt(msg.reward_set.pox_ustx_threshold) + : 50_000n * 1_000_000n, // 50,000 STX signers, rewarded_addresses: rewardedAddresses, }; @@ -1198,6 +1210,7 @@ export function parseNewBlockMessage( pox_v1_unlock_height: msg.pox_v1_unlock_height, pox_v2_unlock_height: msg.pox_v2_unlock_height, pox_v3_unlock_height: msg.pox_v3_unlock_height, + pox_v4_unlock_height: msg.pox_v4_unlock_height, poxSetSigners: poxSetSigners, }; diff --git a/src/pox-helpers.ts b/src/event-stream/pox-constants.ts similarity index 58% rename from src/pox-helpers.ts rename to src/event-stream/pox-constants.ts index f35bd444ff..37f4e2d330 100644 --- a/src/pox-helpers.ts +++ b/src/event-stream/pox-constants.ts @@ -1,19 +1,3 @@ -/** Names for synthetic events generated for the pox-2, pox-3, and pox-4 contracts */ -export enum SyntheticPoxEventName { - HandleUnlock = 'handle-unlock', - StackStx = 'stack-stx', - StackIncrease = 'stack-increase', - StackExtend = 'stack-extend', - DelegateStx = 'delegate-stx', - DelegateStackStx = 'delegate-stack-stx', - DelegateStackIncrease = 'delegate-stack-increase', - DelegateStackExtend = 'delegate-stack-extend', - StackAggregationCommit = 'stack-aggregation-commit', - StackAggregationCommitIndexed = 'stack-aggregation-commit-indexed', - StackAggregationIncrease = 'stack-aggregation-increase', - RevokeDelegateStx = 'revoke-delegate-stx', // Only guaranteed to be present in pox-4 -} - const BOOT_ADDR_MAINNET = 'SP000000000000000000002Q6VF78'; const BOOT_ADDR_TESTNET = 'ST000000000000000000002AMW42H'; @@ -21,6 +5,7 @@ const POX_1_CONTRACT_NAME = 'pox'; export const POX_2_CONTRACT_NAME = 'pox-2'; export const POX_3_CONTRACT_NAME = 'pox-3'; export const POX_4_CONTRACT_NAME = 'pox-4'; +export const POX_5_CONTRACT_NAME = 'pox-5'; export const PoxContractIdentifier = { pox1: { @@ -39,6 +24,10 @@ export const PoxContractIdentifier = { mainnet: `${BOOT_ADDR_MAINNET}.${POX_4_CONTRACT_NAME}`, testnet: `${BOOT_ADDR_TESTNET}.${POX_4_CONTRACT_NAME}`, }, + pox5: { + mainnet: `${BOOT_ADDR_MAINNET}.${POX_5_CONTRACT_NAME}`, + testnet: `${BOOT_ADDR_TESTNET}.${POX_5_CONTRACT_NAME}`, + }, } as const; export const PoxContractIdentifiers = Object.values(PoxContractIdentifier).flatMap( diff --git a/src/event-stream/pox-event-parsing.ts b/src/event-stream/pox-event-parsing.ts deleted file mode 100644 index 5411b0124c..0000000000 --- a/src/event-stream/pox-event-parsing.ts +++ /dev/null @@ -1,512 +0,0 @@ -import { - DbPoxSyntheticBaseEventData, - DbPoxSyntheticDelegateStackExtendEvent, - DbPoxSyntheticDelegateStackIncreaseEvent, - DbPoxSyntheticDelegateStackStxEvent, - DbPoxSyntheticDelegateStxEvent, - DbPoxSyntheticEventData, - DbPoxSyntheticHandleUnlockEvent, - DbPoxSyntheticRevokeDelegateStxEvent, - DbPoxSyntheticStackAggregationCommitEvent, - DbPoxSyntheticStackAggregationCommitIndexedEvent, - DbPoxSyntheticStackAggregationIncreaseEvent, - DbPoxSyntheticStackExtendEvent, - DbPoxSyntheticStackIncreaseEvent, - DbPoxSyntheticStackStxEvent, -} from '../datastore/common.js'; -import { ClarityTypeID, decodeClarityValue } from '@stacks/codec'; -import type { - ClarityValue, - ClarityValueAbstract, - ClarityValueBuffer, - ClarityValueOptional, - ClarityValueOptionalNone, - ClarityValueOptionalSome, - ClarityValuePrincipalContract, - ClarityValuePrincipalStandard, - ClarityValueResponse, - ClarityValueStringAscii, - ClarityValueTuple, - ClarityValueUInt, -} from '@stacks/codec'; -import { poxAddressToBtcAddress } from '@stacks/stacking'; -import { SyntheticPoxEventName } from '../pox-helpers.js'; -import { bufferToHex, coerceToBuffer, logger } from '@stacks/api-toolkit'; - -function tryClarityPoxAddressToBtcAddress( - poxAddr: - | PoxSyntheticEventAddr - | ClarityValueOptionalSome - | ClarityValueOptionalNone, - network: 'mainnet' | 'testnet' | 'devnet' | 'mocknet' -): { btcAddr: string | null; raw: Buffer } { - let btcAddr: string | null = null; - if (poxAddr.type_id === ClarityTypeID.OptionalNone) { - return { - btcAddr, - raw: Buffer.alloc(0), - }; - } - if (poxAddr.type_id === ClarityTypeID.OptionalSome) { - poxAddr = poxAddr.value; - } - try { - btcAddr = poxAddressToBtcAddress( - coerceToBuffer(poxAddr.data.version.buffer)[0], - coerceToBuffer(poxAddr.data.hashbytes.buffer), - network - ); - } catch (e) { - logger.debug( - `Error encoding PoX address version: ${poxAddr.data.version.buffer}, hashbytes: ${poxAddr.data.hashbytes.buffer} to bitcoin address: ${e}` - ); - btcAddr = null; - } - return { - btcAddr, - raw: Buffer.concat([ - coerceToBuffer(poxAddr.data.version.buffer), - coerceToBuffer(poxAddr.data.hashbytes.buffer), - ]), - }; -} - -type PoxSyntheticEventAddr = ClarityValueTuple<{ - hashbytes: ClarityValueBuffer; - version: ClarityValueBuffer; -}>; - -type PoXSyntheticEventData = ClarityValueTuple<{ - name: ClarityValueStringAscii; - balance: ClarityValueUInt; - stacker: ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - locked: ClarityValueUInt; - 'burnchain-unlock-height': ClarityValueUInt; - data: ClarityValueTuple; -}>; - -interface PoxSyntheticPrintEventTypes { - [SyntheticPoxEventName.HandleUnlock]: { - 'first-cycle-locked': ClarityValueUInt; - 'first-unlocked-cycle': ClarityValueUInt; - }; - [SyntheticPoxEventName.StackStx]: { - 'lock-amount': ClarityValueUInt; - 'lock-period': ClarityValueUInt; - 'pox-addr': PoxSyntheticEventAddr; - 'start-burn-height': ClarityValueUInt; - 'unlock-burn-height': ClarityValueUInt; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.StackIncrease]: { - 'increase-by': ClarityValueUInt; - 'total-locked': ClarityValueUInt; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.StackExtend]: { - 'extend-count': ClarityValueUInt; - 'unlock-burn-height': ClarityValueUInt; - 'pox-addr': PoxSyntheticEventAddr; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.DelegateStx]: { - 'amount-ustx': ClarityValueUInt; - 'delegate-to': ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - 'unlock-burn-height': ClarityValueOptionalSome | ClarityValueOptionalNone; - 'pox-addr': PoxSyntheticEventAddr | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; // TODO: add this to core node - }; - [SyntheticPoxEventName.DelegateStackStx]: { - 'lock-amount': ClarityValueUInt; - 'unlock-burn-height': ClarityValueUInt; - 'pox-addr': PoxSyntheticEventAddr; - 'start-burn-height': ClarityValueUInt; - 'lock-period': ClarityValueUInt; - delegator: ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.DelegateStackIncrease]: { - 'pox-addr': PoxSyntheticEventAddr; - 'increase-by': ClarityValueUInt; - 'total-locked': ClarityValueUInt; - delegator: ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; // TODO: add this to core node - }; - [SyntheticPoxEventName.DelegateStackExtend]: { - 'pox-addr': PoxSyntheticEventAddr; - 'unlock-burn-height': ClarityValueUInt; - 'extend-count': ClarityValueUInt; - delegator: ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; // TODO: add this to core node - }; - [SyntheticPoxEventName.StackAggregationCommit]: { - 'pox-addr': PoxSyntheticEventAddr; - 'reward-cycle': ClarityValueUInt; - 'amount-ustx': ClarityValueUInt; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.StackAggregationCommitIndexed]: { - 'pox-addr': PoxSyntheticEventAddr; - 'reward-cycle': ClarityValueUInt; - 'amount-ustx': ClarityValueUInt; - 'signer-key'?: ClarityValueBuffer | ClarityValueOptionalNone; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.StackAggregationIncrease]: { - 'pox-addr': PoxSyntheticEventAddr; - 'reward-cycle': ClarityValueUInt; - 'amount-ustx': ClarityValueUInt; - 'reward-cycle-index'?: ClarityValueUInt; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; - [SyntheticPoxEventName.RevokeDelegateStx]: { - 'delegate-to': ClarityValuePrincipalStandard | ClarityValuePrincipalContract; - 'end-cycle-id'?: ClarityValueOptional; - 'start-cycle-id'?: ClarityValueUInt; - }; -} - -function clarityPrincipalToFullAddress( - principal: ClarityValuePrincipalStandard | ClarityValuePrincipalContract -): string { - if (principal.type_id === ClarityTypeID.PrincipalStandard) { - return principal.address; - } else if (principal.type_id === ClarityTypeID.PrincipalContract) { - return `${principal.address}.${principal.contract_name}`; - } - throw new Error( - `Unexpected Clarity value type for principal: ${(principal as ClarityValue).type_id}` - ); -} - -// TODO: this and the logic referencing it can be removed once the "stale" data issue is fixed in -// https://github.com/stacks-network/stacks-blockchain/pull/3318 -const PATCH_EVENT_BALANCES = true; - -export function decodePoxSyntheticPrintEvent( - rawClarityData: string, - network: 'mainnet' | 'testnet' | 'devnet' | 'mocknet' -): DbPoxSyntheticEventData | null { - const decoded = decodeClarityValue(rawClarityData); - if (decoded.type_id === ClarityTypeID.ResponseError) { - logger.info(`Received ResponseError when decoding Pox synthetic print event: ${decoded.repr}`); - return null; - } - if (decoded.type_id !== ClarityTypeID.ResponseOk) { - const valCommon: ClarityValueAbstract = decoded; - throw new Error( - `Unexpected PoX synthetic event Clarity type ID, expected ResponseOk, got ${valCommon.type_id}: ${valCommon.repr}` - ); - } - if (decoded.value.type_id !== ClarityTypeID.Tuple) { - throw new Error( - `Unexpected PoX synthetic event Clarity type ID, expected Tuple, got ${decoded.value.type_id}` - ); - } - const opData = (decoded.value as PoXSyntheticEventData).data; - - const baseEventData: DbPoxSyntheticBaseEventData = { - stacker: clarityPrincipalToFullAddress(opData.stacker), - locked: BigInt(opData.locked.value), - balance: BigInt(opData.balance.value), - burnchain_unlock_height: BigInt(opData['burnchain-unlock-height'].value), - pox_addr: null, - pox_addr_raw: null, - }; - - const eventName = opData.name.data as keyof PoxSyntheticPrintEventTypes; - if (opData.name.type_id !== ClarityTypeID.StringAscii) { - throw new Error( - `Unexpected PoX synthetic event name type, expected StringAscii, got ${opData.name.type_id}` - ); - } - - const eventData = opData.data.data; - if (opData.data.type_id !== ClarityTypeID.Tuple) { - throw new Error( - `Unexpected PoX synthetic event data payload type, expected Tuple, got ${opData.data.type_id}` - ); - } - - if ('pox-addr' in eventData) { - const eventPoxAddr = eventData['pox-addr'] as - | PoxSyntheticEventAddr - | ClarityValueOptionalSome - | ClarityValueOptionalNone; - const encodedArr = tryClarityPoxAddressToBtcAddress(eventPoxAddr, network); - baseEventData.pox_addr = encodedArr.btcAddr; - baseEventData.pox_addr_raw = bufferToHex(encodedArr.raw); - } - - switch (eventName) { - case SyntheticPoxEventName.HandleUnlock: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticHandleUnlockEvent = { - ...baseEventData, - name: eventName, - data: { - first_cycle_locked: BigInt(d['first-cycle-locked'].value), - first_unlocked_cycle: BigInt(d['first-unlocked-cycle'].value), - }, - }; - if (PATCH_EVENT_BALANCES) { - // Note: `burnchain_unlock_height` is correct for `handle-unlock`, and does not need "patched" like the others - parsedData.balance += parsedData.locked; - } - return parsedData; - } - case SyntheticPoxEventName.StackStx: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackStxEvent = { - ...baseEventData, - name: eventName, - data: { - lock_amount: BigInt(d['lock-amount'].value), - lock_period: BigInt(d['lock-period'].value), - start_burn_height: BigInt(d['start-burn-height'].value), - unlock_burn_height: BigInt(d['unlock-burn-height'].value), - signer_key: - d['signer-key']?.type_id === ClarityTypeID.Buffer ? d['signer-key'].buffer : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.burnchain_unlock_height = parsedData.data.unlock_burn_height; - parsedData.balance -= parsedData.data.lock_amount; - parsedData.locked = parsedData.data.lock_amount; - } - return parsedData; - } - case SyntheticPoxEventName.StackIncrease: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackIncreaseEvent = { - ...baseEventData, - name: eventName, - data: { - increase_by: BigInt(d['increase-by'].value), - total_locked: BigInt(d['total-locked'].value), - signer_key: - d['signer-key']?.type_id === ClarityTypeID.Buffer ? d['signer-key'].buffer : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.balance -= parsedData.data.increase_by; - parsedData.locked += parsedData.data.increase_by; - } - return parsedData; - } - case SyntheticPoxEventName.StackExtend: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackExtendEvent = { - ...baseEventData, - name: eventName, - data: { - extend_count: BigInt(d['extend-count'].value), - unlock_burn_height: BigInt(d['unlock-burn-height'].value), - signer_key: - d['signer-key']?.type_id === ClarityTypeID.Buffer ? d['signer-key'].buffer : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.burnchain_unlock_height = parsedData.data.unlock_burn_height; - } - return parsedData; - } - case SyntheticPoxEventName.DelegateStx: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticDelegateStxEvent = { - ...baseEventData, - name: eventName, - data: { - amount_ustx: BigInt(d['amount-ustx'].value), - delegate_to: clarityPrincipalToFullAddress(d['delegate-to']), - unlock_burn_height: - d['unlock-burn-height'].type_id === ClarityTypeID.OptionalSome - ? BigInt(d['unlock-burn-height'].value.value) - : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - if (parsedData.data.unlock_burn_height) { - parsedData.burnchain_unlock_height = parsedData.data.unlock_burn_height; - } - } - return parsedData; - } - case SyntheticPoxEventName.DelegateStackStx: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticDelegateStackStxEvent = { - ...baseEventData, - name: eventName, - data: { - lock_amount: BigInt(d['lock-amount'].value), - unlock_burn_height: BigInt(d['unlock-burn-height'].value), - start_burn_height: BigInt(d['start-burn-height'].value), - lock_period: BigInt(d['lock-period'].value), - delegator: clarityPrincipalToFullAddress(d['delegator']), - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.burnchain_unlock_height = parsedData.data.unlock_burn_height; - parsedData.balance -= parsedData.data.lock_amount; - parsedData.locked = parsedData.data.lock_amount; - } - return parsedData; - } - case SyntheticPoxEventName.DelegateStackIncrease: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticDelegateStackIncreaseEvent = { - ...baseEventData, - name: eventName, - data: { - increase_by: BigInt(d['increase-by'].value), - total_locked: BigInt(d['total-locked'].value), - delegator: clarityPrincipalToFullAddress(d['delegator']), - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.balance -= parsedData.data.increase_by; - parsedData.locked += parsedData.data.increase_by; - } - return parsedData; - } - case SyntheticPoxEventName.DelegateStackExtend: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticDelegateStackExtendEvent = { - ...baseEventData, - name: eventName, - data: { - unlock_burn_height: BigInt(d['unlock-burn-height'].value), - extend_count: BigInt(d['extend-count'].value), - delegator: clarityPrincipalToFullAddress(d['delegator']), - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - if (PATCH_EVENT_BALANCES) { - parsedData.burnchain_unlock_height = parsedData.data.unlock_burn_height; - } - return parsedData; - } - case SyntheticPoxEventName.StackAggregationCommit: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackAggregationCommitEvent = { - ...baseEventData, - name: eventName, - data: { - reward_cycle: BigInt(d['reward-cycle'].value), - amount_ustx: BigInt(d['amount-ustx'].value), - signer_key: - d['signer-key']?.type_id === ClarityTypeID.Buffer ? d['signer-key'].buffer : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - return parsedData; - } - case SyntheticPoxEventName.StackAggregationCommitIndexed: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackAggregationCommitIndexedEvent = { - ...baseEventData, - name: eventName, - data: { - reward_cycle: BigInt(d['reward-cycle'].value), - amount_ustx: BigInt(d['amount-ustx'].value), - signer_key: - d['signer-key']?.type_id === ClarityTypeID.Buffer ? d['signer-key'].buffer : null, - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - return parsedData; - } - case SyntheticPoxEventName.StackAggregationIncrease: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticStackAggregationIncreaseEvent = { - ...baseEventData, - name: eventName, - data: { - reward_cycle: BigInt(d['reward-cycle'].value), - amount_ustx: BigInt(d['amount-ustx'].value), - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - return parsedData; - } - case SyntheticPoxEventName.RevokeDelegateStx: { - const d = eventData as PoxSyntheticPrintEventTypes[typeof eventName]; - const parsedData: DbPoxSyntheticRevokeDelegateStxEvent = { - ...baseEventData, - name: eventName, - data: { - delegate_to: clarityPrincipalToFullAddress(d['delegate-to']), - end_cycle_id: - d['end-cycle-id']?.type_id === ClarityTypeID.OptionalSome - ? BigInt(d['end-cycle-id'].value.value) - : null, - start_cycle_id: d['start-cycle-id']?.value ? BigInt(d['start-cycle-id'].value) : null, - }, - }; - return parsedData; - } - default: - throw new Error(`Unexpected PoX synthetic event data name: ${opData.name.data}`); - } -} diff --git a/src/event-stream/reader.ts b/src/event-stream/reader.ts index 80799cca20..d5ac1682bc 100644 --- a/src/event-stream/reader.ts +++ b/src/event-stream/reader.ts @@ -4,10 +4,12 @@ import { ClarityTypeID, decodeClarityValue, decodeClarityValueList, + decodePoxSyntheticEvent, decodeStacksAddress, decodeTransaction, PostConditionAuthFlag, PostConditionModeID, + Pox4EventName, PrincipalTypeID, TransactionVersion, TxPayloadTypeID, @@ -20,12 +22,10 @@ import type { ClarityValueTuple, ClarityValueUInt, DecodedTxResult, + Pox4EventStackStx, + Pox4EventDelegateStx, } from '@stacks/codec'; -import { - DbMicroblockPartial, - DbPoxSyntheticDelegateStxEvent, - DbPoxSyntheticStackStxEvent, -} from '../datastore/common.js'; +import { DbMicroblockPartial } from '../datastore/common.js'; import { NotImplementedError } from '../errors.js'; import { getEnumDescription, @@ -48,8 +48,7 @@ import { } from '@stacks/transactions'; import { poxAddressToTuple } from '@stacks/stacking'; import { c32ToB58 } from 'c32check'; -import { decodePoxSyntheticPrintEvent } from './pox-event-parsing.js'; -import { PoxContractIdentifiers, SyntheticPoxEventName } from '../pox-helpers.js'; +import { PoxContractIdentifiers } from './pox-constants.js'; import { bufferToHex, logger } from '@stacks/api-toolkit'; import { hexToBytes } from '@stacks/common'; import { @@ -83,7 +82,7 @@ function createTransactionFromCoreBtcStxLockEvent( txResult: string, txId: string, /** also pox-3 compatible */ - stxStacksPox2Event: DbPoxSyntheticStackStxEvent | undefined + stxStacksPox2Event: Pox4EventStackStx | undefined ): DecodedTxResult { const resultCv = decodeClarityValue< ClarityValueResponse< @@ -256,7 +255,7 @@ function createTransactionFromCoreBtcStxLockEventPox4( function createTransactionFromCoreBtcDelegateStxEventPox4( chainId: ChainID, contractEvent: NewBlockContractEvent, - decodedEvent: DbPoxSyntheticDelegateStxEvent, + decodedEvent: Pox4EventDelegateStx, burnOpData: BurnchainOpDelegateStx, txResult: string, txId: string @@ -342,7 +341,7 @@ function createTransactionFromCoreBtcDelegateStxEventPox4( function createTransactionFromCoreBtcDelegateStxEvent( chainId: ChainID, contractEvent: NewBlockContractEvent, - decodedEvent: DbPoxSyntheticDelegateStxEvent, + decodedEvent: Pox4EventDelegateStx, txResult: string, txId: string ): DecodedTxResult { @@ -523,6 +522,8 @@ export function parseMessageTransaction( let rawTx: DecodedTxResult; let txSender: string; let sponsorAddress: string | undefined = undefined; + + // BTC transactions if (coreTx.raw_tx === '0x00') { const events = allEvents.filter(event => event.txid === coreTx.txid); if (events.length === 0) { @@ -544,7 +545,7 @@ export function parseMessageTransaction( ) .map(e => { const network = getChainIDNetwork(chainId); - const decodedEvent = decodePoxSyntheticPrintEvent(e.contract_event.raw_value, network); + const decodedEvent = decodePoxSyntheticEvent(e.contract_event.raw_value, network); if (decodedEvent) { return { contractEvent: e, @@ -573,7 +574,7 @@ export function parseMessageTransaction( txSender = burnOpData.sender.address; } else if (stxLockEvent) { const stxStacksPoxEvent = - poxEvent?.decodedEvent.name === SyntheticPoxEventName.StackStx + poxEvent?.decodedEvent.name === Pox4EventName.StackStx ? poxEvent.decodedEvent : undefined; rawTx = createTransactionFromCoreBtcStxLockEvent( @@ -587,7 +588,7 @@ export function parseMessageTransaction( txSender = stxLockEvent.stx_lock_event.locked_address; } else if ( poxEvent && - poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx && + poxEvent.decodedEvent.name === Pox4EventName.DelegateStx && poxEvent.contractEvent.contract_event.contract_identifier?.split('.')?.[1] === 'pox-4' && coreTx.burnchain_op && 'delegate_stx' in coreTx.burnchain_op @@ -601,7 +602,7 @@ export function parseMessageTransaction( coreTx.txid ); txSender = coreTx.burnchain_op.delegate_stx.sender.address; - } else if (poxEvent && poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx) { + } else if (poxEvent && poxEvent.decodedEvent.name === Pox4EventName.DelegateStx) { rawTx = createTransactionFromCoreBtcDelegateStxEvent( chainId, poxEvent.contractEvent, diff --git a/tests/api/cache/cache-control.test.ts b/tests/api/cache/cache-control.test.ts index 08db308307..585cb54c1a 100644 --- a/tests/api/cache/cache-control.test.ts +++ b/tests/api/cache/cache-control.test.ts @@ -17,7 +17,7 @@ import { beforeEach, afterEach, describe, test } from 'node:test'; import assert from 'node:assert/strict'; import { assertMatchesObject } from '../test-helpers.ts'; import { STACKS_TESTNET } from '@stacks/network'; -import { SyntheticPoxEventName } from '../../../src/pox-helpers.ts'; +import { Pox4EventName } from '@stacks/codec'; describe('cache-control tests', () => { let db: PgWriteStore; @@ -123,6 +123,7 @@ describe('cache-control tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -296,6 +297,7 @@ describe('cache-control tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1138,17 +1140,18 @@ describe('cache-control tests', () => { block_height: 2, canonical: true, stacker: stacker, - locked: 1000n, - balance: 5000n, - burnchain_unlock_height: 200n, + locked: '1000', + balance: '5000', + burnchain_unlock_height: '200', pox_addr: null, pox_addr_raw: null, - name: SyntheticPoxEventName.StackStx, + pox_version: 'pox4', + name: Pox4EventName.StackStx, data: { - lock_amount: 1000n, - lock_period: 1n, - start_burn_height: 100n, - unlock_burn_height: 200n, + lock_amount: '1000', + lock_period: '1', + start_burn_height: '100', + unlock_burn_height: '200', signer_key: '0x0011223344', end_cycle_id: null, start_cycle_id: null, diff --git a/tests/api/datastore/datastore.test.ts b/tests/api/datastore/datastore.test.ts index 24a8bc9f80..0aca348b66 100644 --- a/tests/api/datastore/datastore.test.ts +++ b/tests/api/datastore/datastore.test.ts @@ -392,6 +392,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -578,6 +579,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -820,6 +822,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], })), }); @@ -1024,6 +1027,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], })), }); @@ -1317,6 +1321,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx2, @@ -1331,6 +1336,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx3, @@ -1345,6 +1351,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2109,6 +2116,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2208,6 +2216,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2315,6 +2324,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2424,6 +2434,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2564,6 +2575,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2662,6 +2674,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2759,6 +2772,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -3023,6 +3037,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: { ...tx2, raw_tx: '0x' }, @@ -3037,6 +3052,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -3561,6 +3577,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -3625,6 +3642,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -4146,6 +4164,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -4213,6 +4232,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -4430,6 +4450,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -4471,6 +4492,7 @@ describe('postgres datastore', () => { index_block_hash: '0xcc', burn_block_height: 123, block_count: 3, + bond_count: 0, mempool_tx_count: 0, microblock_count: 0, microblock_hash: undefined, @@ -4544,6 +4566,7 @@ describe('postgres datastore', () => { index_block_hash: '0xcc', burn_block_height: 123, block_count: 3, + bond_count: 0, microblock_count: 0, microblock_hash: undefined, microblock_sequence: undefined, @@ -4599,6 +4622,7 @@ describe('postgres datastore', () => { block_hash: '0x44bb', block_height: 4, burn_block_height: 123, + bond_count: 0, index_block_hash: '0xddbb', microblock_count: 0, microblock_hash: undefined, @@ -5144,6 +5168,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -5166,6 +5191,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx3, @@ -5180,6 +5206,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -5239,6 +5266,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -5387,6 +5415,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -5478,6 +5507,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -5623,6 +5653,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx2, @@ -5637,6 +5668,7 @@ describe('postgres datastore', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/datastore/other.test.ts b/tests/api/datastore/other.test.ts index 5f659e4045..454a9bc255 100644 --- a/tests/api/datastore/other.test.ts +++ b/tests/api/datastore/other.test.ts @@ -138,6 +138,7 @@ describe('other tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/mempool/mempool.test.ts b/tests/api/mempool/mempool.test.ts index afdb0b7f68..80d169ba78 100644 --- a/tests/api/mempool/mempool.test.ts +++ b/tests/api/mempool/mempool.test.ts @@ -1465,6 +1465,7 @@ describe('mempool tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1665,6 +1666,7 @@ describe('mempool tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/microblocks/microblock.test.ts b/tests/api/microblocks/microblock.test.ts index 78f58ab388..350b722fd1 100644 --- a/tests/api/microblocks/microblock.test.ts +++ b/tests/api/microblocks/microblock.test.ts @@ -387,6 +387,7 @@ describe('microblock tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -544,6 +545,7 @@ describe('microblock tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: mbTx2, @@ -558,6 +560,7 @@ describe('microblock tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/principal/address.test.ts b/tests/api/principal/address.test.ts index e01cd7c706..12ad8e85c7 100644 --- a/tests/api/principal/address.test.ts +++ b/tests/api/principal/address.test.ts @@ -242,6 +242,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], })), }); @@ -1535,6 +1536,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], } as DataStoreTxEventData; }); dataStoreTxs.push({ @@ -1550,6 +1552,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }); dataStoreTxs.push({ tx: { ...contractCall, raw_tx: '0x' }, @@ -1577,6 +1580,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }); await db.update({ block: block, @@ -2651,6 +2655,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2898,6 +2903,7 @@ describe('address tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }); } await db.updateMicroblocks(mbData); diff --git a/tests/api/smart-contracts/smart-contract.test.ts b/tests/api/smart-contracts/smart-contract.test.ts index 2fb2e1777e..57b35a6b57 100644 --- a/tests/api/smart-contracts/smart-contract.test.ts +++ b/tests/api/smart-contracts/smart-contract.test.ts @@ -151,6 +151,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx2, @@ -165,6 +166,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -295,6 +297,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -412,6 +415,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1605,6 +1609,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx2, @@ -1619,6 +1624,7 @@ describe('smart contract tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/test-builders.ts b/tests/api/test-builders.ts index 534f790874..ec0cd2fa6d 100644 --- a/tests/api/test-builders.ts +++ b/tests/api/test-builders.ts @@ -280,6 +280,7 @@ function testTx(args?: TestTxArgs): DataStoreTxEventData { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }; if ( data.tx.type_id === DbTxTypeId.SmartContract || diff --git a/tests/api/transactions/search.test.ts b/tests/api/transactions/search.test.ts index 0ec0b41212..1b5461e7c8 100644 --- a/tests/api/transactions/search.test.ts +++ b/tests/api/transactions/search.test.ts @@ -365,6 +365,7 @@ describe('search tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }; @@ -1317,6 +1318,7 @@ describe('search tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: stxTx2, @@ -1331,6 +1333,7 @@ describe('search tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: smartContractTx, @@ -1345,6 +1348,7 @@ describe('search tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }; diff --git a/tests/api/transactions/tx.test.ts b/tests/api/transactions/tx.test.ts index 14df6487a7..a159d7055c 100644 --- a/tests/api/transactions/tx.test.ts +++ b/tests/api/transactions/tx.test.ts @@ -277,6 +277,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: dbTx2, @@ -291,6 +292,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: dbTx3, @@ -305,6 +307,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -453,6 +456,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -678,6 +682,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -831,6 +836,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1019,6 +1025,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1406,6 +1413,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1651,6 +1659,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -1862,6 +1871,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2020,6 +2030,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2815,6 +2826,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -2940,6 +2952,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -3596,6 +3609,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); @@ -3871,6 +3885,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, { tx: tx2, @@ -3885,6 +3900,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }; @@ -4192,6 +4208,7 @@ describe('tx tests', () => { pox2Events: [], pox3Events: [], pox4Events: [], + pox5Events: [], }, ], }); diff --git a/tests/api/v3/blocks.test.ts b/tests/api/v3/blocks.test.ts index 620af3e506..f795a48197 100644 --- a/tests/api/v3/blocks.test.ts +++ b/tests/api/v3/blocks.test.ts @@ -7,6 +7,7 @@ import * as assert from 'node:assert/strict'; import { TestBlockBuilder } from '../test-builders.ts'; import { DbTxStatus, DbTxTypeId } from '../../../src/datastore/common.ts'; import { hex } from '../test-helpers.ts'; +import { I32_MAX } from '../../../src/helpers.ts'; const SENDER = 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27'; const RECIPIENT = 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6'; @@ -122,7 +123,7 @@ describe('blocks', () => { sender_address: SENDER, token_transfer_recipient_address: RECIPIENT, token_transfer_amount: 100n, - token_transfer_memo: '0x', + token_transfer_memo: '0x0d0000000568656c6c6f', }) .build() ); @@ -166,7 +167,10 @@ describe('blocks', () => { token_transfer: { recipient: RECIPIENT, amount: '100', - memo: '0x', + memo: { + hex: '0x0d0000000568656c6c6f', + repr: 'hello', + }, }, }); assert.deepEqual(body.results[1], { @@ -403,6 +407,80 @@ describe('blocks', () => { assert.equal(body.cursor.current, '2:0:0'); }); + test('should allow block-boundary cursors for anchored transactions', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: hex(0x1001), + microblock_sequence: I32_MAX, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: '/extended/v3/blocks/1/transactions', + query: { + limit: '1', + cursor: '1:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].tx_id, hex(0x1001)); + assert.deepEqual(body.cursor, { + next: null, + previous: null, + current: `1:${I32_MAX}:0`, + }); + }); + + test('should preserve exact height:0:0 transaction cursors', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: hex(0x1001), + tx_index: 0, + microblock_sequence: 0, + }) + .addTx({ + tx_id: hex(0x1002), + tx_index: 1, + microblock_sequence: I32_MAX, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: '/extended/v3/blocks/1/transactions', + query: { + limit: '1', + cursor: '1:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].tx_id, hex(0x1001)); + assert.deepEqual(body.cursor, { + next: null, + previous: `1:${I32_MAX}:1`, + current: '1:0:0', + }); + }); + test('should return 304 when ETag matches and refresh ETag per block', async () => { await db.update( new TestBlockBuilder({ diff --git a/tests/api/v3/mempool.test.ts b/tests/api/v3/mempool.test.ts index 112b4e55eb..ec469fbac3 100644 --- a/tests/api/v3/mempool.test.ts +++ b/tests/api/v3/mempool.test.ts @@ -72,7 +72,7 @@ describe('mempool', () => { sender_address: 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27', token_transfer_recipient_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', token_transfer_amount: 500n, - token_transfer_memo: '0x', + token_transfer_memo: '0x0d0000000568656c6c6f', fee_rate: 250n, nonce: 3, }), @@ -133,7 +133,10 @@ describe('mempool', () => { token_transfer: { recipient: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', amount: '500', - memo: '0x', + memo: { + hex: '0x0d0000000568656c6c6f', + repr: 'hello', + }, }, }); }); @@ -544,6 +547,7 @@ describe('mempool', () => { sender_address: SENDER, nonce: 1, type_id: DbTxTypeId.TokenTransfer, + token_transfer_memo: '0x0d0000000568656c6c6f', }) .build() ); diff --git a/tests/api/v3/principals.test.ts b/tests/api/v3/principals.test.ts index c4dae9b09f..93d3ebcda8 100644 --- a/tests/api/v3/principals.test.ts +++ b/tests/api/v3/principals.test.ts @@ -7,6 +7,7 @@ import * as assert from 'node:assert/strict'; import { TestBlockBuilder } from '../test-builders.ts'; import { DbTxStatus, DbTxTypeId } from '../../../src/datastore/common.ts'; import { hex } from '../test-helpers.ts'; +import { I32_MAX } from '../../../src/helpers.ts'; describe('principals', () => { let db: PgWriteStore; @@ -81,6 +82,7 @@ describe('principals', () => { status: DbTxStatus.Success, sender_address: sender, nonce: indexIdIndex, + token_transfer_memo: '0x0d0000000568656c6c6f', }); for (let i = 0; i < stxEventCount; i++) { block.addTxStxEvent({ @@ -177,7 +179,10 @@ describe('principals', () => { token_transfer: { recipient: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', amount: '100', - memo: '0x', + memo: { + hex: '0x0d0000000568656c6c6f', + repr: 'hello', + }, }, }, involvement: 'sender', @@ -314,6 +319,120 @@ describe('principals', () => { previous: '10:0:4', current: '9:0:4', }); + + // Fetch a partial page that has fewer than `limit` newer rows. The previous + // cursor should still point back to the first available row. + const partialPreviousPage = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/principals/${emptyPrincipal}/transactions`, + query: { + limit: '5', + cursor: '12:0:2', + }, + }); + assert.equal(partialPreviousPage.statusCode, 200); + const partialPreviousBody = JSON.parse(partialPreviousPage.body); + assert.equal(partialPreviousBody.results.length, 5); + assert.deepEqual(partialPreviousBody.cursor, { + next: '11:0:2', + previous: '12:0:4', + current: '12:0:2', + }); + }); + + test('should allow block-boundary cursors for anchored transactions', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 3, + block_hash: hex(3), + index_block_hash: hex(3), + parent_index_block_hash: hex(2), + parent_block_hash: hex(2), + }) + .addTx({ + tx_id: hex(0x3001), + block_hash: hex(3), + index_block_hash: hex(3), + block_time: 3000, + burn_block_height: 3, + burn_block_time: 3000, + tx_index: 0, + microblock_sequence: I32_MAX, + sender_address: emptyPrincipal, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/principals/${emptyPrincipal}/transactions`, + query: { + limit: '1', + cursor: '3:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].transaction.tx_id, hex(0x3001)); + assert.deepEqual(body.cursor, { + next: null, + previous: null, + current: `3:${I32_MAX}:0`, + }); + }); + + test('should preserve exact height:0:0 transaction cursors', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 3, + block_hash: hex(3), + index_block_hash: hex(3), + parent_index_block_hash: hex(2), + parent_block_hash: hex(2), + }) + .addTx({ + tx_id: hex(0x3001), + block_hash: hex(3), + index_block_hash: hex(3), + block_time: 3000, + burn_block_height: 3, + burn_block_time: 3000, + tx_index: 0, + microblock_sequence: 0, + sender_address: emptyPrincipal, + }) + .addTx({ + tx_id: hex(0x3002), + block_hash: hex(3), + index_block_hash: hex(3), + block_time: 3000, + burn_block_height: 3, + burn_block_time: 3000, + tx_index: 1, + microblock_sequence: I32_MAX, + sender_address: emptyPrincipal, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/principals/${emptyPrincipal}/transactions`, + query: { + limit: '1', + cursor: '3:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].transaction.tx_id, hex(0x3001)); + assert.deepEqual(body.cursor, { + next: null, + previous: `3:${I32_MAX}:1`, + current: '3:0:0', + }); }); test('should return 304 when ETag matches and refresh ETag on new principal activity', async () => { @@ -375,6 +494,7 @@ describe('principals', () => { status: DbTxStatus.Success, sender_address: testAddr1, nonce: 100, + token_transfer_memo: '0x0d0000000568656c6c6f', }) .build() ); diff --git a/tests/api/v3/transactions.test.ts b/tests/api/v3/transactions.test.ts index 74c52e3f03..400fbdf4db 100644 --- a/tests/api/v3/transactions.test.ts +++ b/tests/api/v3/transactions.test.ts @@ -9,6 +9,7 @@ import { DbTxStatus, DbTxTypeId } from '../../../src/datastore/common.ts'; import { stringAsciiCV, uintCV } from '@stacks/transactions'; import { bufferToHex } from '@stacks/api-toolkit'; import { hex } from '../test-helpers.ts'; +import { I32_MAX } from '../../../src/helpers.ts'; describe('transactions', () => { let db: PgWriteStore; @@ -90,7 +91,7 @@ describe('transactions', () => { sender_address: 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27', token_transfer_recipient_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', token_transfer_amount: 100n, - token_transfer_memo: '0x', + token_transfer_memo: '0x0d0000000568656c6c6f', }) .build() ); @@ -131,7 +132,10 @@ describe('transactions', () => { token_transfer: { recipient: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', amount: '100', - memo: '0x', + memo: { + hex: '0x0d0000000568656c6c6f', + repr: 'hello', + }, }, }); assert.deepEqual(body.results[1], { @@ -224,6 +228,80 @@ describe('transactions', () => { }); }); + test('should allow block-boundary cursors for anchored transactions', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: hex(0x1001), + microblock_sequence: I32_MAX, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: '/extended/v3/transactions', + query: { + limit: '1', + cursor: '1:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].tx_id, hex(0x1001)); + assert.deepEqual(body.cursor, { + next: null, + previous: null, + current: `1:${I32_MAX}:0`, + }); + }); + + test('should preserve exact height:0:0 transaction cursors', async () => { + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: hex(0x1001), + tx_index: 0, + microblock_sequence: 0, + }) + .addTx({ + tx_id: hex(0x1002), + tx_index: 1, + microblock_sequence: I32_MAX, + }) + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: '/extended/v3/transactions', + query: { + limit: '1', + cursor: '1:0:0', + }, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + assert.equal(body.results.length, 1); + assert.equal(body.results[0].tx_id, hex(0x1001)); + assert.deepEqual(body.cursor, { + next: null, + previous: `1:${I32_MAX}:1`, + current: '1:0:0', + }); + }); + test('should return 304 when ETag matches and refresh ETag when chain tip changes', async () => { await db.update( new TestBlockBuilder({ @@ -479,4 +557,144 @@ describe('transactions', () => { assert.notEqual(otherTx.headers['etag'], etag); }); }); + + describe('/v3/transactions/:tx_id/events', () => { + const sender = 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27'; + const recipient = 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6'; + + test('should return transaction events sorted by event_index across event tables', async () => { + const txId = hex(0x7001); + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: txId, + tx_index: 0, + event_count: 5, + }) + .addTxStxEvent({ amount: 101n }) + .addTxFtEvent({ amount: 202n, sender, recipient }) + .addTxNftEvent({ sender, recipient }) + .addTxStxLockEvent({ locked_amount: 303, unlock_height: 555, locked_address: sender }) + .addTxContractLogEvent() + .build() + ); + + const response = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/transactions/${txId}/events`, + }); + assert.equal(response.statusCode, 200); + const body = JSON.parse(response.body); + + assert.equal(body.total, 5); + assert.equal(body.limit, 20); + assert.deepEqual(body.cursor, { + next: null, + previous: null, + current: '0', + }); + assert.equal(body.results.length, 5); + assert.deepEqual( + body.results.map((event: { event_index: number }) => event.event_index), + [0, 1, 2, 3, 4] + ); + assert.deepEqual( + body.results.map((event: { type: string }) => event.type), + ['stx_asset', 'ft_asset', 'nft_asset', 'stx_lock', 'contract_log'] + ); + }); + + test('should cursor paginate transaction events by event_index', async () => { + const txId = hex(0x7002); + await db.update( + new TestBlockBuilder({ + block_height: 1, + index_block_hash: hex(1), + parent_index_block_hash: hex(0), + parent_block_hash: hex(0), + }) + .addTx({ + tx_id: txId, + tx_index: 0, + event_count: 5, + }) + .addTxStxEvent() + .addTxFtEvent({ sender, recipient }) + .addTxNftEvent({ sender, recipient }) + .addTxStxLockEvent({ locked_address: sender }) + .addTxContractLogEvent() + .build() + ); + + const page1 = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/transactions/${txId}/events`, + query: { + limit: '2', + }, + }); + assert.equal(page1.statusCode, 200); + const body1 = JSON.parse(page1.body); + assert.equal(body1.total, 5); + assert.equal(body1.results.length, 2); + assert.deepEqual( + body1.results.map((event: { event_index: number }) => event.event_index), + [0, 1] + ); + assert.deepEqual(body1.cursor, { + next: '2', + previous: null, + current: '0', + }); + + const page2 = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/transactions/${txId}/events`, + query: { + limit: '2', + cursor: '2', + }, + }); + assert.equal(page2.statusCode, 200); + const body2 = JSON.parse(page2.body); + assert.equal(body2.total, 5); + assert.equal(body2.results.length, 2); + assert.deepEqual( + body2.results.map((event: { event_index: number }) => event.event_index), + [2, 3] + ); + assert.deepEqual(body2.cursor, { + next: '4', + previous: '0', + current: '2', + }); + + const page3 = await api.fastifyApp.inject({ + method: 'GET', + url: `/extended/v3/transactions/${txId}/events`, + query: { + limit: '2', + cursor: '4', + }, + }); + assert.equal(page3.statusCode, 200); + const body3 = JSON.parse(page3.body); + assert.equal(body3.total, 5); + assert.equal(body3.results.length, 1); + assert.deepEqual( + body3.results.map((event: { event_index: number }) => event.event_index), + [4] + ); + assert.deepEqual(body3.cursor, { + next: null, + previous: '2', + current: '4', + }); + }); + }); }); diff --git a/tests/krypton/pox-4-burnchain-delegate-stx/pox-4-burnchain-delegate-stx.test.ts b/tests/krypton/pox-4-burnchain-delegate-stx/pox-4-burnchain-delegate-stx.test.ts index bfa868fbf3..8e3ae14cf9 100644 --- a/tests/krypton/pox-4-burnchain-delegate-stx/pox-4-burnchain-delegate-stx.test.ts +++ b/tests/krypton/pox-4-burnchain-delegate-stx/pox-4-burnchain-delegate-stx.test.ts @@ -12,7 +12,7 @@ import { BootContractAddress } from '../../../src/helpers.ts'; import * as btc from 'bitcoinjs-lib'; import { b58ToC32, c32ToB58 } from 'c32check'; import supertest from 'supertest'; -import { PoxContractIdentifier } from '../../../src/pox-helpers.ts'; +import { PoxContractIdentifier } from '../../../src/event-stream/pox-constants.ts'; import { decodeClarityValue } from '@stacks/codec'; import type { ClarityValueUInt } from '@stacks/codec'; import { decodeBtcAddress, poxAddressToBtcAddress } from '@stacks/stacking'; @@ -428,7 +428,7 @@ describe('PoX-4 - Stack using Bitcoin-chain delegate ops', () => { assert.ok(res !== undefined); assert.equal(res.results.length, 1); assert.equal(res.results[0].name, 'delegate-stack-stx'); - assert.equal(res.results[0].pox_addr, poxAddrPayoutAccount.btcTestnetAddr); + assert.equal(res.results[0].pox_addr, poxAddrPayoutAccount.btcAddr); assert.equal(res.results[0].stacker, account.stxAddr); assert.equal(res.results[0].balance, BigInt(coreBalanceInfo.balance).toString()); assert.equal(res.results[0].locked, testStackAmount.toString());