diff --git a/content/docs/stacks/chainhook/concepts/bitcoin-predicates.mdx b/content/docs/stacks/chainhook/concepts/bitcoin-predicates.mdx deleted file mode 100644 index 957be3f3d..000000000 --- a/content/docs/stacks/chainhook/concepts/bitcoin-predicates.mdx +++ /dev/null @@ -1,425 +0,0 @@ ---- -title: Bitcoin predicates -description: A list of predicates for using Chainhook with Bitcoin. The predicates are specified based on if-this, then-that constructs. ---- - -## `if_this` specifications - -Get any transaction matching a given transaction ID (`txid`). - -```jsonc -// [!code word:txid] -{ - "if_this": { - "scope": "txid", - "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" - } -} -``` - -The `txid` mandatory argument requires a 32-byte hex-encoded type. - ---- - - -Get any transaction, where its `OP_RETURN` payload starts with a set of characters. - -```jsonc -// [!code word:outputs] -// [!code word:starts_with] -{ - "if_this": { - "scope": "outputs", - "op_return": { - "starts_with": "ab" - } - } -} -``` - -The `starts_with` mandatory argument admits an ASCII string type (such as `ab`) or hex encoded bytes (such as `0x6162`). - ---- - -Get any transaction where its `OP_RETURN` payload is equals to a set of characters. - -```jsonc -// [!code word:outputs] -// [!code word:equals] -{ - "if_this": { - "scope": "outputs", - "op_return": { - "equals": "0x616263" - } - } -} -``` - -The mandatory argument for `equals` accepts either an ASCII string type (such as `abc`) or hex encoded bytes (such as `0x616263`). - ---- - -Get any transaction where its `OP_RETURN` payload ends with a set of characters. - -```jsonc -// [!code word:outputs] -// [!code word:ends_with] -{ - "if_this": { - "scope": "outputs", - "op_return": { - "ends_with": "0x6263" - } - } -} -``` - -The mandatory argument for `ends_with` accepts either an ASCII string type (such as `bc`) or hex encoded bytes (such as `0x6263`). - ---- - -Get any transaction with a p2pkh output paying a given recipient. - -```jsonc -// [!code word:outputs] -// [!code word:p2pkh] -{ - "if_this": { - "scope": "outputs", - "p2pkh": { - "equals": "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" - } - } -} -``` - -The `p2pkh` construct admits an ASCII string type: `"mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC"`. - -Or a hex encoded bytes type: `"0x76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac"`. - - -`p2pkh`(Pay-to-Public-Key-Hash) is a Bitcoin transaction output script type that allows users to send funds to a recipient's hashed public key, providing security and privacy by concealing the actual public key. - - ---- - -Get any transaction including a p2sh output paying a given recipient. - -```jsonc -// [!code word:outputs] -// [!code word:p2sh] -{ - "if_this": { - "scope": "outputs", - "p2sh": { - "equals": "2MxDJ723HBJtEMa2a9vcsns4qztxBuC8Zb2" - } - } -} -``` - -The `p2sh` construct accepts the following types: - - A string type: `"2MxDJ723HBJtEMa2a9vcsns4qztxBuC8Zb2"`. - - A hex encoded bytes type: `"0x76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac"`. - - -`p2sh`(Pay-to-Script-Hash) is a Bitcoin transaction output script type that enables users to send funds to a script instead of a public key, allowing for more complex transaction conditions and multi-signature addresses. - - ---- - -Get any transaction, including a `p2wpkh` output paying a given recipient. - -```jsonc -// [!code word:outputs] -// [!code word:p2wpkh] -{ - "if_this": { - "scope": "outputs", - "p2wpkh": { - "equals": "bcrt1qnxknq3wqtphv7sfwy07m7e4sr6ut9yt6ed99jg" - } - } -} -``` - -The `p2wpkh` construct accepts a string type, for example: `"bcrt1qnxknq3wqtphv7sfwy07m7e4sr6ut9yt6ed99jg"`. - - -`p2wpkh`(Pay-to-Witness-Public-Key-Hash) is a Bitcoin transaction output script type used in Segregated Witness (SegWit) that allows for more efficient and secure transactions by separating the witness data from the transaction data and storing it in a separate block. - - ---- - -Get any transaction, including a `p2wsh` output paying a given recipient. - -```jsonc -// [!code word:outputs] -// [!code word:p2wsh] -{ - "if_this": { - "scope": "outputs", - "p2wsh": { - "equals": "bc1qklpmx03a8qkv263gy8te36w0z9yafxplc5kwzc" - } - } -} -``` - -The `p2wsh` construct accepts a string type, for example: "bc1qklpmx03a8qkv263gy8te36w0z9yafxplc5kwzc". - - -`p2wsh` (Pay-to-Witness-Script-Hash) is a Bitcoin transaction output script type used in Segregated Witness (SegWit) that enables users to send funds to a hashed script, allowing for more complex transaction conditions and greater scalability by separating the script from the transaction data. - - - - -Wallet descriptors provide a compact and semi-standardized method for describing how scripts and addresses within a wallet are generated. Chainhook users that want to track addresses derived from an extended pubkey or a multisig-wallet can now rely on this feature instead of defining one predicate per address. -For example, let's say we wanted to track the first 3 addresses generated by the following descriptor: - -``` -wpkh(tprv8ZgxMBicQKsPePxn6j3TjvB2MBzQkuhGgc6oRh2WZancZQgxktcnjZJ44XdsRiw3jNkbVTK9JW6KFHvnRKgAMtSyuBevMJprSkZ4PTfmTgV/84'/1'/0'/0/*) -``` - -This descriptor reads: describe a P2WPKH output with the specified extended public key, and it produces these BIP84 addresses: - -``` -bcrt1qzy2rdyvu8c57qd8exyyp0mw7dk5drsu9ewzdsu -bcrt1qsfsjnagr29m8h3a3vdand2s85cg4cefkcwk2fy -bcrt1qauewfytqe5mtr0xwp786r6fl39kmum2lr65kmj -``` - -The predicate should then be defined like this: - -```json -{ - "if_this": { - "scope": "outputs", - "descriptor": { - "expression": "wpkh(tprv8ZgxMBicQKsPePxn6j3TjvB2MBzQkuhGgc6oRh2WZancZQgxktcnjZJ44XdsRiw3jNkbVTK9JW6KFHvnRKgAMtSyuBevMJprSkZ4PTfmTgV/84'/1'/0'/0/*)", - "range": [0, 3] - } - } -} -``` - - - ---- - -Get any Bitcoin transaction, including a block commitment. Broadcasted payloads include _Proof of Transfer_ reward information. - -```jsonc -// [!code word:stacks_protocol] -// [!code word:block_committed] -{ - "if_this": { - "scope": "stacks_protocol", - "operation": "block_committed" - } -} -``` - -Proof of Transfer (PoT) is the [blockchain consensus mechanism for Stacks](https://www.hiro.so/blog/securing-web3-apps-through-bitcoin-an-overview-of-stacks-consensus-mechanism) where miners can bid BTC for the chance to mine Stacks blocks. - ---- - -Get any transaction, including a key registration operation. - -```jsonc -// [!code word:stacks_protocol] -// [!code word:leader_registered] -{ - "if_this": { - "scope": "stacks_protocol", - "operation": "leader_registered" - } -} -``` - ---- - -Get any transaction including a new Ordinal inscription (inscription revealed and transferred). - -```jsonc -// [!code word:ordinals_protocol] -// [!code word:inscription_feed] -{ - "if_this": { - "scope": "ordinals_protocol", - "operation": "inscription_feed" - } -} -``` - ---- - -Get any transaction, including a STX transfer operation. - -```jsonc -// [!code word:stacks_protocol] -// [!code word:stx_transferred] -{ - "if_this": { - "scope": "stacks_protocol", - "operation": "stx_transferred" - } -} -``` - ---- - -Get any transaction, including a STX lock operation. - -```jsonc -// [!code word:stacks_protocol] -// [!code word:stx_locked] -{ - "if_this": { - "scope": "stacks_protocol", - "operation": "stx_locked" - } -} -``` - -## `then_that` constructs - -The following `then_that` constructs are supported: - -`HTTP Post` block/transaction payload to a given endpoint: - -- `http_post` construct admits: - - url (string type), like `http://localhost:3000/api/v1/wrapBtc` - - authorization_header (string type). - -```jsonc -{ - "then_that": { - "http_post": { - "url": "http://localhost:3000/api/v1/wrapBtc", - "authorization_header": "Bearer cn389ncoiwuencr", - }, - }, -} -``` - -Append events to a file through the filesystem, which is convenient for local tests: - -- `file_append` construct admits: - - path (string type). Path to the file on disk. - -```jsonc -{ - "then_that": { - "file_append": { - "path": "/tmp/events.json", - }, - }, -} -``` - -## Additional configuration knobs available - -The following additional configurations can be used to improve the performance of Chainhook by preventing a full scan of the blockchain. - -Ignore any block before the given block: - -```json -"start_block": 101 -``` - -Ignore any block after the given block: - -```json -"end_block": 201 -``` - -Stop evaluating a chainhook after a given number of occurrences are found: - -```json -"expire_after_occurrence": 1 -``` - -Don't include proofs: - -```json -"include_proof": false -``` - -Don't include proofs: - -```json -"include_proof": false -``` - -Don't include Bitcoin transaction inputs in the payload: - -```json -"include_inputs": false -``` - -Don't include Bitcoin transaction outputs in the payload: - -```json -"include_outputs": false -``` - -Don't include Bitcoin transaction outputs in the payload: - -```json -"include_outputs": false -``` - -Don't include Bitcoin transaction witnesses in the payload: - -```json -"include_witness": false -``` - -Don't include Bitcoin transaction witnesses in the payload: - -```json -"include_witness": false -``` - - - - ```json - { - "chain": "bitcoin", - "uuid": "1", - "name": "Wrap BTC", - "version": 1, - "networks": { - "testnet": { - "if_this": { - "scope": "ordinals_protocol", - "operation": "inscription_feed" - }, - "then_that": { - "http_post": { - "url": "http://localhost:3000/api/v1/ordinals", - "authorization_header": "Bearer cn389ncoiwuencr" - } - }, - "start_block": 10200 - }, - "mainnet": { - "if_this": { - "scope": "ordinals_protocol", - "operation": "inscription_feed" - }, - "then_that": { - "http_post": { - "url": "http://my-protocol.xyz/api/v1/ordinals", - "authorization_header": "Bearer cn389ncoiwuencr" - } - }, - "start_block": 90232 - } - } - } - ``` - - diff --git a/content/docs/stacks/chainhook/concepts/predicate-design.mdx b/content/docs/stacks/chainhook/concepts/predicate-design.mdx index f34a6486e..fff7133b0 100644 --- a/content/docs/stacks/chainhook/concepts/predicate-design.mdx +++ b/content/docs/stacks/chainhook/concepts/predicate-design.mdx @@ -3,62 +3,347 @@ title: Predicate design description: Predicates are the building blocks of Chainhook. --- -The core design of chainhooks revolves around the concept of predicates. Each individual chainhook has a customizable predicate that specifies what information you are scanning for. +The core design of Chainhook revolves around the concept of predicates. Each individual Chainhook has a customizable predicate that specifies what the Bitcoin or Stacks blockchain data you are scanning for. -Predicates are defined in an `if-this`, `then-that` format. You'll write your condition in the `if-this` condition template and use `then-that` to output the result. + + Commands outlined here will require that you have installed Chainhook [directly](/stacks/chainhook/installation). + -## `if-this` +## Predicate design +Below is the general strucure of the `predicate` JSON with its primary elements. These elements and their values are required. -The `if-this` predicate design can use the following attributes to define the predicates. The 'scope' parameter is mandatory to use with any other parameters. +- Chainhook will evaluate the predicate against the specfied blockchain specified in `chain`. +- The `uuid` will be returned in the Chainhook payload, providing a record of the predicate that triggers it. +- Identify your predicate for your DApp using `name` and `version`. ```json -// [!code word:if_this] -{ - "if_this": { - "scope": "txid", - "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" + { + "chain": "stacks", + "uuid": "1", + "name": "STX_Transfer_Predicate", + "version": 1, + "networks": { + // Other configurations + } } -} -``` + ``` -Other available parameters: + + You can use the following command to verify your predicate: + ``` + chainhook predicate check your-predicate-file.json --mainnet + ``` + + +## Networks -- `equals` -- `op_return` -- `ends_with` -- `p2pkh` -- `p2sh` -- `p2wpkh` -- `operation` +The `networks` element contains an object who's key determines the blockchain network you want Chainhook to scan. +- This object's value will contain the `if_this` and then `then_that` specifications. +- Multple networks can be specified in the `networks` element. + +```json + "networks": { + "mainnet": { + // if_this and then_that specifications + // Other configurations + }, + "testnet": { + // if_this and then_that specifications + // Other configurations + }, + } +``` - - For more information on predicate definitions, refer to the [scopes](/stacks/chainhook/references/scopes) reference page. + + For additional information, check out the [Bitcoin scopes](/stacks/chainhook/references/scopes/bitcoin) and [Stacks scopes](/stacks/chainhook/references/scopes/stacks) references pages. -## `then-that` +## if_this specification + +The `predicate` identifies what data you want Chainhook to scan for using the `scope` define within the `if_this` specification. Additional arguments specific to the `scope` will also be passed here. + +```json + // [!code word:if_this] + // [!code word:scope] + { + "if_this": { + "scope": "contract_call", + "contract_identifier": "STJ81C2WPQHFB6XTG518JKPABWM639R2X37VFKJV.simple-vote-v0", + "method": "cast-vote" + } + } +``` + +## then_that specification + +Chainhook delivers the payload when it is triggered by your `predicate` using the `then_that` specification. There are 2 options available: -The `then-that` predicate design can use the following attributes to output the result based on the `if-this` condition defined. +1. `file_append` +2. `http_post` -```jsonc +When choosing to use file_append, specify the path where Chainhook will post the payload data. + +```json +// [!code word:then_that] // [!code word:file_append] { "then_that": { - "file_append": { - "path": "/tmp/events.json", - }, - }, + "file_append": { + "path": "/tmp/events.json" + } + } } ``` -```jsonc +When using `http_post`, specify the endpoint's `url` and `authorization_header`. + +```json +// [!code word:then_that] // [!code word:http_post] { "then_that": { "http_post": { - "url": "https://example.com/api/v1/events", - "authorization_header": "Bearer 1234567890" + "url": "https://webhook.site/abc123456-789e-0fgh-1ijk-23lmno456789", + "authorization_header": "12345" } } } ``` + + Chainhook requires `https` to post to your endpoint. You can use a service like [LocalTunnel](https://github.com/localtunnel/localtunnel) to test locally or a site like [WebhookSite](https://webhook.site). + + +## Blockchain specific configurations + + + + + ```json + { + "chain": "bitcoin", + "uuid": "1", + "name": "Wrap BTC", + "version": 1, + "networks": { + "testnet": { + "if_this": { + "scope": "ordinals_protocol", + "operation": "inscription_feed" + }, + "then_that": { + "http_post": { + "url": "http://localhost:3000/api/v1/ordinals", + "authorization_header": "Bearer cn389ncoiwuencr" + } + }, + "start_block": 10200 + // Additional configurations + }, + "mainnet": { + "if_this": { + "scope": "ordinals_protocol", + "operation": "inscription_feed" + }, + "then_that": { + "http_post": { + "url": "http://my-protocol.xyz/api/v1/ordinals", + "authorization_header": "Bearer cn389ncoiwuencr" + } + }, + "start_block": 90232 + // Additional configurations + } + } + } + ``` + + + + The following additional configurations can be used to improve the performance of Chainhook by preventing a full scan of the blockchain. + + Ignore any block before the given block: + + ```json + "start_block": 101 + ``` + + Ignore any block after the given block: + + ```json + "end_block": 201 + ``` + + Stop evaluating a chainhook after a given number of occurrences are found: + + ```json + "expire_after_occurrence": 1 + ``` + + Don't include proofs: + + ```json + "include_proof": false + ``` + + Don't include proofs: + + ```json + "include_proof": false + ``` + + Don't include Bitcoin transaction inputs in the payload: + + ```json + "include_inputs": false + ``` + + Don't include Bitcoin transaction outputs in the payload: + + ```json + "include_outputs": false + ``` + + Don't include Bitcoin transaction outputs in the payload: + + ```json + "include_outputs": false + ``` + + Don't include Bitcoin transaction witnesses in the payload: + + ```json + "include_witness": false + ``` + + Don't include Bitcoin transaction witnesses in the payload: + + ```json + "include_witness": false + ``` + + + + + The following command allows you to generate a predicate template for the Bitcoin blockchain. + ``` + chainhook predicates new your-bitcoin-predicate.json --bitcoin + ``` + + + + + + + ```json + { + "chain": "stacks", + "uuid": "1", + "name": "Contract-Call-Chainhook", + "version": 1, + "networks": { + "testnet": { + "if_this": { + "scope": "contract_call", + "contract_identifier": "STJ81C2WPQHFB6XTG518JKPABWM639R2X37VFKJV.simple-vote-v0", + "method": "cast-vote" + }, + "then_that": { + "file_append": { + "path": "/tmp/events.json" + } + }, + "start_block": 21443 + // Additional configurations + }, + "mainnet": { + "if_this": { + "scope": "contract_call", + "contract_identifier": "STJ81C2WPQHFB6XTG518JKPABWM639R2X37VFKJV.simple-vote-v0", + "method": "cast-vote" + }, + "then_that": { + "http_post": { + "url": "http://my-protocol.xyz/api/v1/ordinals", + "authorization_header": "Bearer cn389ncoiwuencr" + } + }, + "start_block": 142221 + // Additional configurations + } + } + } + ``` + + + + Thse additional configurations can be used to improve the performance of Chainhook by preventing a full scan of the blockchain: + + Ignore any block before the given block: + + ```json + "start_block": 101 + ``` + + Ignore any block after the given block: + + ```json + "end_block": 201 + ``` + + Stop evaluating chainhook after a given number of occurrences found: + + ```json + "expire_after_occurrence": 1 + ``` + + Include decoded Clarity values in the payload: + + ```json + "decode_clarity_values": true + ``` + + Include the contract ABI for transactions that deploy contracts: + + ```json + "include_contract_abi": true + ``` + + + + + The following command allows you to generate a predicate template for the Stacks blockchain. + ``` + chainhook predicates new your-stacks-predicate.json --stacks + ``` + + + + + + +## Next steps + + + + + + + + diff --git a/content/docs/stacks/chainhook/concepts/stacks-predicates.mdx b/content/docs/stacks/chainhook/concepts/stacks-predicates.mdx deleted file mode 100644 index f8fbbad34..000000000 --- a/content/docs/stacks/chainhook/concepts/stacks-predicates.mdx +++ /dev/null @@ -1,300 +0,0 @@ ---- -title: Stacks predicates -description: A list of predicates for using Chainhook with Stacks. The predicates are specified based on if-this, and then-that constructs. ---- - -## `if_this` specifications - -Get any transaction matching a given transaction ID, `txid`. - -```jsonc -// [!code word:txid] -{ - "if_this": { - "scope": "txid", - "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" - } -} -``` - -The `txid` mandatory argument requires a 32-byte hex-encoded type. - ---- - -Get any Stacks block based on matching constraints. - -```jsonc -// [!code word:block_height] -{ - "if_this": { - "scope": "block_height", - "equals": 154100 - } -} -``` - -The `block_height` argument admits: `equals`, `higher_than`, `lower_than`, `between`. - -The `between` operator can be used by providing an array with two values: - -```jsonc -// [!code word:between] -{ - "if_this": { - "scope": "block_height", - "between": [0, 1000] - } -} -``` - ---- - -Get any transaction related to a given fungible token asset identifier: - -```jsonc -// [!code word:ft_event] -{ - "if_this": { - "scope": "ft_event", - "asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-token::cbtc", - "actions": ["burn"] - } -} -``` - -The `asset_identifier` argument admits an asset identifier to observe, for example: `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-sip10::cbtc`. - -The `actions` argument admits an array of string types constrained to `mint`, `transfer`, and `burn` values, such as `["mint", "burn"]`. - ---- - -Get any transaction related to a given non-fungible token asset identifier. - -The `asset-identifier` argument is mandatory and requires a string type that fully qualifies the asset identifier to observe, for example: `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09::monkeys`. - -The `actions` argument is also mandatory and accepts an array of string types limited to the values `mint`, `transfer`, and `burn`, such as `["mint", "burn"]`. - -```jsonc -// [!code word:nft_event] -{ - "if_this": { - "scope": "nft_event", - "asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09::monkeys", - "actions": ["mint", "transfer", "burn"] - } -} -``` - ---- - -Get any transaction moving STX tokens. - -The `actions` argument is mandatory and accepts an array of string types limited to the values `mint`, `transfer`, `burn`, and `lock`, such as `["mint", "lock"]`. - -```json -// [!code word:stx_event] -{ - "if_this": { - "scope": "stx_event", - "actions": ["transfer", "lock"] - } -} -``` - ---- - -Get any transaction emitting a given print events predicate. - -The `contract-identifier` is a mandatory argument that requires a string type, fully qualifying the contract to observe, for example: `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09`. - -Additionally, you must include either the `contains` or `matches_regex` argument: - -- The `contains` argument accepts a string type, used for matching an event that contains the specified string, for example `vault`. -- The `matches_regex` argument accepts a string type that should be a valid regex, used for matching an event that regex matches with the specified string, such as `(?:^|\\W)vault(?:$|\\W)`. - -The following example uses `contains` argument: - -```jsonc -// [!code word:print_event] -// [!code word:contains] -{ - "if_this": { - "scope": "print_event", - "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", - "contains": "vault" - } -} -``` - -The following example uses `matches_regex` argument: - -```jsonc -// [!code word:print_event] -// [!code word:matches_regex] -{ - "if_this": { - "scope": "print_event", - "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", - "matches_regex": "(?:^|\\W)vault(?:$|\\W)" - } -} -``` - ---- - -Get any transaction calling a specific method for a given contract _directly_. - - -If the observed method is being called by another contract, this predicate won't detect it. - - -The `contract-identifier` is a mandatory argument that requires a string type, fully qualifying the contract to observe, for example `SP000000000000000000002Q6VF78.pox`. - -The `method` is also a mandatory argument that accepts a string type, used for specifying the method to observe, such as `stack-stx`. - -```jsonc -// [!code word:contract_call] -// [!code word:method] -{ - "if_this": { - "scope": "contract_call", - "contract_identifier": "SP000000000000000000002Q6VF78.pox", - "method": "stack-stx" - } -} -``` - ---- - -Get any transaction, including a contract deployment. - -The `deployer` argument is mandatory and accepts a string representing a valid STX address, for example `"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG"`. - -```jsonc -// [!code word:contract_deployment] -// [!code word:deployer] -{ - "if_this": { - "scope": "contract_deployment", - "deployer": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" - } -} -``` - -## `then_that` specifications - -HTTP Post block/transaction payload to a given endpoint. - -The `http_post` construct requires a `url` of type string (for example, `http://localhost:3000/api/v1/wrapBtc`) and an `authorization_header` of type string, which is a secret added to the request's `authorization` header. - -```jsonc -// [!code word:http_post] -// [!code word:url] -// [!code word:authorization_header] -{ - "then_that": { - "http_post": { - "url": "http://localhost:3000/api/v1/wrapBtc", - "authorization_header": "Bearer cn389ncoiwuencr" - } - } -} -``` - ---- - -Append events to a file through the filesystem, which is convenient for local tests. - -The `file_append` construct requires a `path` parameter, which is a string representing the file path on the disk. - - -```jsonc -// [!code word:file_append] -// [!code word:path] -{ - "then_that": { - "file_append": { - "path": "/tmp/events.json" - } - } -} -``` - -## Additional configurations available - -The following additional configurations can be used to improve the performance of chainhook by preventing a full scan of the blockchain: - -Ignore any block before the given block: - -```json -"start_block": 101 -``` - -Ignore any block after the given block: - -```json -"end_block": 201 -``` - -Stop evaluating chainhook after a given number of occurrences found: - -```json -"expire_after_occurrence": 1 -``` - -Include decoded Clarity values in the payload: - -```json -"decode_clarity_values": true -``` - -Include the contract ABI for transactions that deploy contracts: - -```json -"include_contract_abi": true -``` - - - - ```jsonc - { - "chain": "stacks", - "uuid": "1", - "name": "Lorem ipsum", - "version": 1, - "networks": { - "testnet": { - "if_this": { - "scope": "print_event", - "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", - "contains": "vault" - }, - "then_that": { - "http_post": { - "url": "http://localhost:3000/api/v1/vaults", - "authorization_header": "Bearer cn389ncoiwuencr" - } - }, - "start_block": 10200, - "expire_after_occurrence": 5 - }, - "mainnet": { - "if_this": { - "scope": "print_event", - "contract_identifier": "SP456HQKV0RJXZFY1DGX8MNSNYVE3VGZJSRT459863.monkey-sip09", - "contains": "vault" - }, - "then_that": { - "http_post": { - "url": "http://my-protocol.xyz/api/v1/vaults", - "authorization_header": "Bearer cn389ncoiwuencr" - } - }, - "start_block": 90232, - "expire_after_occurrence": 5 - } - } - } - ``` - - diff --git a/content/docs/stacks/chainhook/guides/chainhook-as-a-service-with-stacks-node.mdx b/content/docs/stacks/chainhook/guides/chainhook-as-a-service-with-stacks-node.mdx index 122852eaa..db71f4d5d 100644 --- a/content/docs/stacks/chainhook/guides/chainhook-as-a-service-with-stacks-node.mdx +++ b/content/docs/stacks/chainhook/guides/chainhook-as-a-service-with-stacks-node.mdx @@ -321,8 +321,8 @@ The response should look something like this: description="Learn how to register Chainhooks on devnet." /> \ No newline at end of file diff --git a/content/docs/stacks/chainhook/guides/chainhook-as-a-service.mdx b/content/docs/stacks/chainhook/guides/chainhook-as-a-service.mdx index 0fda3ad80..27d76f9ec 100644 --- a/content/docs/stacks/chainhook/guides/chainhook-as-a-service.mdx +++ b/content/docs/stacks/chainhook/guides/chainhook-as-a-service.mdx @@ -81,7 +81,7 @@ Here is a table of the relevant parameters this guide changes in our configurati ## Scan the blockchain based on predicates -Now that your `bitcoind` and Chainhook configurations are complete, you can define the Chainhook [predicate scope](/stacks/chainhook/references/scopes) you would like to scan for. +Now that your `bitcoind` and Chainhook configurations are complete, you can define the Chainhook [Bitcoin predicate scope](/stacks/chainhook/references/scopes/bitcoin) you would like to scan for. These predicates are where you specify the `if_this / then_that` pattern to trigger Chainhook to deliver a result (either a file appendation or an HTTP POST request). @@ -330,8 +330,8 @@ You can also run chainhook service by passing multiple predicates, ie `chainhook description="Learn how to register Chainhooks on devnet." /> \ No newline at end of file diff --git a/content/docs/stacks/chainhook/guides/observing-contract-calls.mdx b/content/docs/stacks/chainhook/guides/observing-contract-calls.mdx index 544fc55c8..d45a4374e 100644 --- a/content/docs/stacks/chainhook/guides/observing-contract-calls.mdx +++ b/content/docs/stacks/chainhook/guides/observing-contract-calls.mdx @@ -47,9 +47,9 @@ There are 3 main components to your `predicate` that you need to address: 2. Defining the scope and targeting the function you want to observe 3. Defining the payload destination -To begin, you need to configure the `predicate` to target the voting contract. A valid `predicate` must: +To begin, you need to configure the `predicate` to target the voting contract: -- Specify the `testnet` network object +- Specify `testnet` in the network object. - Set the `start_block` property to 21443This value represents the block height that our voting contract was deployed at.. ```json title="contract-call-chainhook.json" @@ -71,10 +71,6 @@ To begin, you need to configure the `predicate` to target the voting contract. A } ``` - - This block height of `21443` represents when the voting contract was deployed. For more details on optional configurations, check out the [Stacks predicates](/stacks/chainhook/concepts/stacks-predicates#additional-configurations-available) page. - - Next, define the scope of the `predicate` within the `if_this` specification. The `contract_call` scope allows Chainhook to observe blockchain data when the specified function is directly called from its contract. @@ -100,12 +96,9 @@ The `contract_call` scope allows Chainhook to observe blockchain data when the s ``` -Finally, define how Chainhook delivers the payload when it is triggered by your `predicate` using the `then_that` specification. There are 2 options available: - -1. `file_append` -2. `http_post` +Finally, define how Chainhook delivers the payload when it is triggered by your `predicate` using the `then_that` specification. -When choosing to use `file_append`, specify the path where Chainhook will post the payload data. +Using `file_append`, specify the path where Chainhook will post the payload data. ```json title="contract-call-chainhook.json" // [!code word:file_append] @@ -118,20 +111,6 @@ When choosing to use `file_append`, specify the path where Chainhook will post t } ``` -When using `http_post`, specify the endpoint's `url` and `authorization_header`. - -```json title="contract-call-chainhook.json" -// [!code word:http_post] -{ - "then_that": { - "http_post": { - "url": "https://webhook.site/abc123456-789e-0fgh-1ijk-23lmno456789", - "authorization_header": "12345" - } - } -} -``` - ```json @@ -163,7 +142,9 @@ When using `http_post`, specify the endpoint's `url` and `authorization_header`. - Chainhook requires `https` to post to your endpoint. You can use a service like [LocalTunnel](https://github.com/localtunnel/localtunnel) to test locally or a site like [WebhookSite](https://webhook.site). + + For more details on optional configurations, other predicate elements and scopes, check out the [Predicate Design](/stacks/chainhook/concepts/predicate-design) and [Stacks scopes](/stacks/chainhook/references/scopes/stacks) pages. + @@ -405,8 +386,8 @@ The following is an example of how you might store your information in a databas description="Learn how to create a Chainhook using the Hiro Platform." /> diff --git a/content/docs/stacks/chainhook/guides/register-chainhooks-on-devnet.mdx b/content/docs/stacks/chainhook/guides/register-chainhooks-on-devnet.mdx index 0b09f2acd..f9ccf335f 100644 --- a/content/docs/stacks/chainhook/guides/register-chainhooks-on-devnet.mdx +++ b/content/docs/stacks/chainhook/guides/register-chainhooks-on-devnet.mdx @@ -36,7 +36,7 @@ These should be situated in the root of your project directory. The files can ei -For examples on how to define your predicates, refer to the [scopes](/stacks/chainhook/references/scopes) reference page. +For examples on how to define your predicates, refer to the [scopes](/stacks/chainhook/references/scopes/stacks) reference page. ## Start devnet diff --git a/content/docs/stacks/chainhook/meta.json b/content/docs/stacks/chainhook/meta.json index 2b6e736d5..d4936f3fe 100644 --- a/content/docs/stacks/chainhook/meta.json +++ b/content/docs/stacks/chainhook/meta.json @@ -8,14 +8,18 @@ "quickstart", "---Concepts---", "concepts/predicate-design", - "concepts/stacks-predicates", - "concepts/bitcoin-predicates", + "concepts/general-configration", + "concepts/if-this-specification", + "concepts/then-that-specification", + "concepts/stacks-predicate-configurations", + "concepts/bitcoin-predicate-configurations", "---Guides---", "guides/register-chainhooks-on-devnet", "guides/chainhook-as-a-service", "guides/chainhook-as-a-service-with-stacks-node", "guides/observing-contract-calls", "---Reference---", - "...references" + "references/scopes/bitcoin", + "references/scopes/stacks" ] } \ No newline at end of file diff --git a/content/docs/stacks/chainhook/references/scopes/bitcoin.mdx b/content/docs/stacks/chainhook/references/scopes/bitcoin.mdx new file mode 100644 index 000000000..ae2cff059 --- /dev/null +++ b/content/docs/stacks/chainhook/references/scopes/bitcoin.mdx @@ -0,0 +1,395 @@ +--- +title: Bitcoin Scopes +description: Reference guide for available scopes for Bitcoin. +toc: false +--- + +import { Root, API, APIExample } from '@/components/layout'; +import { Property } from 'fumadocs-ui/components/api' + +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +import { InlineCode } from '@/components/inline-code'; + +Bitcoin scopes are parameters you use to define the `if this` specification logic of your Chainhook on the Bitcoin blockchain. In other words, scopes specify what on-chain events you are looking to monitor and track. For more information on Chainhook design, please view [predicate design](/stacks/chainhook/concepts/predicate-design). + + + + + +
+ +

`txid`

+ +The `txid` scope allows you to query transactions based on their transaction ID. This is particularly useful for tracking specific transactions or auditing transaction histories. + +## Parameters + + + +The `equals` property is a 32 byte hex encoded type used to specify the exact transaction ID to match. + + + +
+ + + + + + + By transaction ID + + + + + ```json + // [!code word:txid] + { + "if_this": { + "scope": "txid", + "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" // [!code highlight] + } + } + ``` + + + + + +
+ + + +
+ +

`outputs`

+ +The `outputs` scope allows you to query blocks based on the payload returned by the specified operation. + +## Parameters + + + +The `operation` property is used to identify the Bitcoin operation that will trigger Chainhook as it observes Bitcoin transactions. The available operations are `op_return`, `p2pkh`, `p2sh`,`p2wpkh` and `p2wsh`. + + + +
+ + + + + + + Using op_return + + + Using p2pkh + + + Using p2sh + + + Using p2wpkh + + + Using p2wsh + + + Using descriptor + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "op_return": { // [!code highlight] + "equals": "0xbtc21000042stx016" // [!code highlight] + } + } + } + ``` + + + + The `equals` property specifies the exact characters of the string or 32 byte encoded hex to match. + + + The `starts_with` property specifies the starting characters of the string or 32 byte encoded hex to match. + + + The `ends_with` property specifies the ending characters of the string or 32 byte encoded hex to match. + + + + + The `op_return` operation allows for `equals`, `starts_with` and `ends_with` for matching against the desired input. + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "p2kph": { // [!code highlight] + "equals": "0xbtc21000042stx016" // [!code highlight] + } + } + } + ``` + + + + The `equals` property specifies the exact characters of the string or 32 byte encoded hex to match. + + + + + `p2pkh`(Pay-to-Public-Key-Hash) is a Bitcoin transaction output script type that allows users to send funds to a recipient's hashed public key, providing security and privacy by concealing the actual public key. + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "p2sh": { // [!code highlight] + "equals": "0xbtc21000042stx016" // [!code highlight] + } + } + } + ``` + + + + The `equals` property specifies the exact characters of the string or 32 byte encoded hex to match. + + + + + `p2sh`(Pay-to-Script-Hash) is a Bitcoin transaction output script type that enables users to send funds to a script instead of a public key, allowing for more complex transaction conditions and multi-signature addresses. + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "p2wpkh": { // [!code highlight] + "equals": "0xbtc21000042stx016" // [!code highlight] + } + } + } + ``` + + + + The `equals` property specifies the exact characters of the string to match. + + + + + `p2wpkh`(Pay-to-Witness-Public-Key-Hash) is a Bitcoin transaction output script type used in Segregated Witness (SegWit) that allows for more efficient and secure transactions by separating the witness data from the transaction data and storing it in a separate block. + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "p2wsh": { // [!code highlight] + "equals": "0xbtc21000042stx016" // [!code highlight] + } + } + } + ``` + + + + The `equals` property specifies the exact characters of the string to match. + + + + + `p2wsh` (Pay-to-Witness-Script-Hash) is a Bitcoin transaction output script type used in Segregated Witness (SegWit) that enables users to send funds to a hashed script, allowing for more complex transaction conditions and greater scalability by separating the script from the transaction data. + + + + + ```json + // [!code word:outputs] + { + "if_this": { + "scope": "outputs", + "descriptor": { + "expression": "wpkh(tprv8ZgxMBicQKsPePxn6j3TjvB2MBzQkuhGgc6oRh2WZancZQgxktcnjZJ44XdsRiw3jNkbVTK9JW6KFHvnRKgAMtSyuBevMJprSkZ4PTfmTgV/84'/1'/0'/0/*)", // [!code highlight] + "range": [0, 3] // [!code highlight] + } + } + } + ``` + + + + The `expression` property specifies the exact characters of the string to match. + + + The `range` property specifies and array of integers representing the desired indexes. + + + + + + + Wallet descriptors provide a compact and semi-standardized method for describing how scripts and addresses within a wallet are generated. Chainhook users that want to track addresses derived from an extended pubkey or a multisig-wallet can now rely on this feature instead of defining one predicate per address. + For example, let's say we wanted to track the first 3 addresses generated by the following descriptor: + + ``` + wpkh(tprv8ZgxMBicQKsPePxn6j3TjvB2MBzQkuhGgc6oRh2WZancZQgxktcnjZJ44XdsRiw3jNkbVTK9JW6KFHvnRKgAMtSyuBevMJprSkZ4PTfmTgV/84'/1'/0'/0/*) + ``` + + This descriptor reads: describe a P2WPKH output with the specified extended public key, and it produces these BIP84 addresses: + + ``` + bcrt1qzy2rdyvu8c57qd8exyyp0mw7dk5drsu9ewzdsu + bcrt1qsfsjnagr29m8h3a3vdand2s85cg4cefkcwk2fy + bcrt1qauewfytqe5mtr0xwp786r6fl39kmum2lr65kmj + ``` + + + + + + + + +
+ + + +
+ +

`stacks_protocol`

+ +The `stacks_protocol` scope allows you query Bitcoin transactions related to the Stacks Proof of Transfer (PoT) [concensus mechanism](https://www.hiro.so/blog/securing-web3-apps-through-bitcoin-an-overview-of-stacks-consensus-mechanism). + +## Parameters + + + +The `operation` property is used to identify the Stacks operation that will trigger Chainhook as it observes Bitcoin transactions. The available operations are `block_committed`, `leader_registered`, `inscription_feed`, `stx_transferred` and `stx_locked`. + + + +
+ + + + + + + Using block_committed + + + Using leader_registered + + + Using inscription_feed + + + Using stx_transferred + + + Using stx_locked + + + + + ```json + // [!code word:stacks_protocol] + { + "if_this": { + "scope": "stacks_protocol", + "operation": "block_committed" // [!code highlight] + } + } + ``` + + + + ```json + // [!code word:stacks_protocol] + { + "if_this": { + "scope": "stacks_protocol", + "operation": "leader_registered" // [!code highlight] + } + } + ``` + + + + ```json + // [!code word:stacks_protocol] + { + "if_this": { + "scope": "stacks_protocol", + "operation": "inscription_feed" // [!code highlight] + } + } + ``` + + + `inscription_feed` observes the reveal and transfer of an Ordinal inscription. + + + + + ```json + // [!code word:stacks_protocol] + { + "if_this": { + "scope": "stacks_protocol", + "operation": "stx_transferred" // [!code highlight] + } + } + ``` + + + + ```json + // [!code word:stacks_protocol] + { + "if_this": { + "scope": "stacks_protocol", + "operation": "stx_locked" // [!code highlight] + } + } + ``` + + + + + +
+ +
\ No newline at end of file diff --git a/content/docs/stacks/chainhook/references/scopes.mdx b/content/docs/stacks/chainhook/references/scopes/stacks.mdx similarity index 83% rename from content/docs/stacks/chainhook/references/scopes.mdx rename to content/docs/stacks/chainhook/references/scopes/stacks.mdx index 955112d6b..f74b6a561 100644 --- a/content/docs/stacks/chainhook/references/scopes.mdx +++ b/content/docs/stacks/chainhook/references/scopes/stacks.mdx @@ -1,25 +1,18 @@ --- -title: Scopes -description: Reference guide for available scopes for Stacks and Bitcoin. +title: Stacks Scopes +description: Reference guide for available scopes for Stacks. toc: false --- import { Root, API, APIExample } from '@/components/layout'; import { Property } from 'fumadocs-ui/components/api' -import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { InlineCode } from '@/components/inline-code'; -Scopes are parameters you use to define the "if this" logic of your chainhook. In other words, scopes specify what on-chain events you are looking to monitor and track. For more information on Chainhook design, please view [predicate design](/stacks/chainhook/concepts/predicate-design). - -## Installation - -```console -brew install chainhook -``` +Stacks scopes are parameters you use to define the `if this` specification logic of your Chainhook on the Stacks blockchain. In other words, scopes specify what on-chain events you are looking to monitor and track. For more information on Chainhook design, please view [predicate design](/stacks/chainhook/concepts/predicate-design). @@ -55,7 +48,7 @@ The `equals` property is a 32 bytes hex encoded type used to specify the exact t { "if_this": { "scope": "txid", - "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" + "equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" // [!code highlight] } } ``` @@ -125,40 +118,40 @@ The `between` property specifies a range of block heights to match, inclusive of { "if_this": { "scope": "block_height", - "equals": 141200 + "equals": 141200 // [!code highlight] } } ``` ```json - // [!code word:higher_than] + // [!code word:block_height] { "if_this": { "scope": "block_height", - "higher_than": 10000 + "higher_than": 10000 // [!code highlight] } } ``` ```json - // [!code word:lower_than] + // [!code word:block_height] { "if_this": { "scope": "block_height", - "lower_than": 10000 + "lower_than": 10000 // [!code highlight] } } ``` ```json - // [!code word:between] + // [!code word:block_height] { "if_this": { "scope": "block_height", - "between": [0, 10000] + "between": [0, 10000] // [!code highlight] } } ``` @@ -181,7 +174,7 @@ The `ft_transfer` scope allows you to query transactions based on fungible token -The `asset_identifier` property specifies the fully qualified asset identifier to observe, such as `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-token::cbtc`. +The `asset_identifier` property specifies the fully qualified asset identifier to observe. @@ -211,12 +204,13 @@ The `actions` property specifies the types of token actions to observe, such as "if_this": { "scope": "ft_transfer", "asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-token::cbtc", - "actions": ["transfer"] + "actions": ["transfer"] // [!code highlight] } } ``` + // [!code word:ft_transfer] ```json { "if_this": { @@ -245,7 +239,7 @@ The `nft_transfer` scope allows you to query transactions based on non-fungible -The `asset_identifier` property specifies the fully qualified asset identifier to observe, such as `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09::monkeys`. +The `asset_identifier` property specifies the fully qualified asset identifier to observe. @@ -275,13 +269,14 @@ The `actions` property specifies the types of NFT actions to observe, such as `m "if_this": { "scope": "nft_transfer", "asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09::monkeys", - "actions": ["mint"] + "actions": ["mint"] // [!code highlight] } } ``` ```json + // [!code word:nft_transfer] { "if_this": { "scope": "nft_transfer", @@ -320,7 +315,10 @@ The `actions` property specifies the types of STX token actions to observe, such - Scoping by STX token transfer + By STX token transfer + + + Passing all actions @@ -329,7 +327,18 @@ The `actions` property specifies the types of STX token actions to observe, such { "if_this": { "scope": "stx_transfer", - "actions": ["transfer", "lock"] + "actions": ["transfer"] // [!code highlight] + } + } + ``` + + + ```json + // [!code word:stx_transfer] + { + "if_this": { + "scope": "stx_transfer", + "actions": ["mint", "transfer", "burn", "lock"] // [!code highlight] } } ``` @@ -375,7 +384,10 @@ The `matches_regex` property is used for matching an event that regex matches wi - Scoping by print event + Using contains + + + Using matches_regex @@ -385,7 +397,19 @@ The `matches_regex` property is used for matching an event that regex matches wi "if_this": { "scope": "print_event", "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", - "contains": "monkey" + "contains": "monkey" // [!code highlight] + } + } + ``` + + + ```json + // [!code word:print_event] + { + "if_this": { + "scope": "print_event", + "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", + "matches_regex": "(?:^|\\W)monkey(?:$|\\W)" // [!code highlight] } } ``` @@ -435,7 +459,7 @@ The `method` property specifies the specific method within the contract to obser "if_this": { "scope": "contract_call", "contract_identifier": "SP000000000000000000002Q6VF78.pox", - "method": "stack-stx" + "method": "stack-stx" // [!code highlight] } } ``` @@ -483,22 +507,22 @@ The `implement_trait` property specifies the contract trait to observe. ```json - // [!code word:deployer] + // [!code word:contract_deployment] { "if_this": { "scope": "contract_deployment", - "deployer": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + "deployer": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" // [!code highlight] } } ``` ```json - // [!code word:implement_trait] + // [!code word:contract_deployment] { "if_this": { "scope": "contract_deployment", - "implement_trait": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sip09-protocol" + "implement_trait": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sip09-protocol" // [!code highlight] } } ``` diff --git a/vercel.json b/vercel.json index f900744aa..4d7b816d6 100644 --- a/vercel.json +++ b/vercel.json @@ -13,6 +13,8 @@ ], "redirects": [ { "source": "/chainhook/:path*", "destination": "/stacks/chainhook", "permanent": true }, + { "source": "/stacks/chainhook/concepts/bitcoin-predicates", "destination": "/stacks/chainhook/concepts/predicate-design", "permanent": true }, + { "source": "/stacks/chainhook/concepts/stacks-predicates", "destination": "/stacks/chainhook/concepts/predicate-design", "permanent": true }, { "source": "/clarinet/:path*", "destination": "/stacks/clarinet", "permanent": true }, { "source": "/stacks/explorer/:path*", "destination": "https://explorer.hiro.so", "permanent": true }, { "source": "/explorer/:path*", "destination": "https://explorer.hiro.so", "permanent": true },