diff --git a/Cargo.lock b/Cargo.lock index 7620bfe82..ff62ff446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,7 +510,7 @@ dependencies = [ [[package]] name = "chainhook-sdk" -version = "0.12.7" +version = "0.12.10" dependencies = [ "base58", "base64 0.21.5", diff --git a/components/chainhook-sdk/Cargo.toml b/components/chainhook-sdk/Cargo.toml index c43e3086f..694610b22 100644 --- a/components/chainhook-sdk/Cargo.toml +++ b/components/chainhook-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainhook-sdk" -version = "0.12.7" +version = "0.12.10" description = "Stateless Transaction Indexing Engine for Stacks and Bitcoin" license = "GPL-3.0" edition = "2021" diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index cc1900b63..1f5b306fa 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -288,6 +288,10 @@ pub fn serialize_bitcoin_transactions_to_json<'a>( }; metadata.insert("ordinal_operations".into(), json!(ordinals_ops)); + if let Some(ref brc20) = transaction.metadata.brc20_operation { + metadata.insert("brc20_operation".into(), json!(brc20)); + } + metadata.insert( "proof".into(), json!(proofs.get(&transaction.transaction_identifier)), diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs index 2f879f1ff..f2024a289 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs @@ -1,12 +1,15 @@ +use std::collections::HashSet; + use super::super::types::MatchingRule; use super::*; +use crate::chainhooks::types::InscriptionFeedData; use crate::indexer::tests::helpers::accounts; use crate::indexer::tests::helpers::bitcoin_blocks::generate_test_bitcoin_block; use crate::indexer::tests::helpers::transactions::generate_test_tx_bitcoin_p2pkh_transfer; use crate::types::BitcoinTransactionMetadata; use chainhook_types::bitcoin::TxOut; -use chainhook_types::BitcoinNetwork; +use chainhook_types::{BitcoinNetwork, Brc20Operation, Brc20TokenDeployData}; use test_case::test_case; #[test_case( @@ -193,3 +196,78 @@ fn it_serdes_occurrence_payload( let _: BitcoinChainhookOccurrencePayload = serde_json::from_slice(&payload[..]).unwrap(); } + +#[test_case( + "pepe".to_string(); + "including brc20 data" +)] +fn it_serdes_brc20_payload(tick: String) { + let transaction = BitcoinTransactionData { + transaction_identifier: TransactionIdentifier { + hash: "0xc6191000459e4c58611103216e44547e512c01ee04119462644ee09ce9d8e8bb".to_string(), + }, + operations: vec![], + metadata: BitcoinTransactionMetadata { + inputs: vec![], + outputs: vec![], + ordinal_operations: vec![], + stacks_operations: vec![], + brc20_operation: Some(Brc20Operation::Deploy(Brc20TokenDeployData { + tick, + max: "21000000.000000".to_string(), + lim: "1000.000000".to_string(), + dec: "6".to_string(), + address: "3P4WqXDbSLRhzo2H6MT6YFbvBKBDPLbVtQ".to_string(), + inscription_id: + "c6191000459e4c58611103216e44547e512c01ee04119462644ee09ce9d8e8bbi0".to_string(), + self_mint: false, + })), + proof: None, + fee: 0, + index: 0, + }, + }; + let block = generate_test_bitcoin_block(0, 0, vec![transaction.clone()], None); + let mut meta_protocols = HashSet::::new(); + meta_protocols.insert(OrdinalsMetaProtocol::Brc20); + let chainhook = &BitcoinChainhookSpecification { + uuid: "uuid".into(), + owner_uuid: None, + name: "name".into(), + network: BitcoinNetwork::Mainnet, + version: 0, + blocks: None, + start_block: None, + end_block: None, + expire_after_occurrence: None, + predicate: BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed( + InscriptionFeedData { + meta_protocols: Some(meta_protocols), + }, + )), + action: HookAction::Noop, + include_proof: false, + include_inputs: false, + include_outputs: false, + include_witness: false, + enabled: true, + expired_at: None, + }; + let trigger = BitcoinTriggerChainhook { + chainhook, + apply: vec![(vec![&transaction], &block)], + rollback: vec![], + }; + let payload = serde_json::to_vec(&serialize_bitcoin_payload_to_json( + &trigger, + &HashMap::new(), + )) + .unwrap(); + + let deserialized: BitcoinChainhookOccurrencePayload = + serde_json::from_slice(&payload[..]).unwrap(); + assert!(deserialized.apply[0].block.transactions[0] + .metadata + .brc20_operation + .is_some()); +} diff --git a/components/client/typescript/package-lock.json b/components/client/typescript/package-lock.json index cb668eaa5..dd0427e3f 100644 --- a/components/client/typescript/package-lock.json +++ b/components/client/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hirosystems/chainhook-client", - "version": "1.9.0", + "version": "1.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@hirosystems/chainhook-client", - "version": "1.9.0", + "version": "1.10.0", "license": "Apache 2.0", "dependencies": { "@fastify/type-provider-typebox": "^3.2.0", diff --git a/components/client/typescript/package.json b/components/client/typescript/package.json index 6597aea49..9731e83a9 100644 --- a/components/client/typescript/package.json +++ b/components/client/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@hirosystems/chainhook-client", - "version": "1.9.0", + "version": "1.10.0", "description": "Chainhook TypeScript client", "main": "./dist/index.js", "typings": "./dist/index.d.ts", diff --git a/components/client/typescript/src/server.ts b/components/client/typescript/src/server.ts index 7d952727a..49ac1707c 100644 --- a/components/client/typescript/src/server.ts +++ b/components/client/typescript/src/server.ts @@ -28,6 +28,8 @@ const ServerOptionsSchema = Type.Object({ wait_for_chainhook_node: Type.Optional(Type.Boolean({ default: true })), /** Validate the JSON schema of received chainhook payloads and report errors when invalid */ validate_chainhook_payloads: Type.Optional(Type.Boolean({ default: false })), + /** Validate the authorization token sent by the server is correct. */ + validate_token_authorization: Type.Optional(Type.Boolean({ default: true })), /** Size limit for received chainhook payloads (default 40MB) */ body_limit: Type.Optional(Type.Number({ default: 41943040 })), /** Node type: `chainhook` or `ordhook` */ @@ -186,6 +188,7 @@ export async function buildServer( } async function isEventAuthorized(request: FastifyRequest, reply: FastifyReply) { + if (!(serverOpts.validate_token_authorization ?? true)) return; const authHeader = request.headers.authorization; if (authHeader && authHeader === `Bearer ${serverOpts.auth_token}`) { return; @@ -200,39 +203,32 @@ export async function buildServer( > = (fastify, options, done) => { const compiledPayloadSchema = TypeCompiler.Compile(PayloadSchema); fastify.addHook('preHandler', isEventAuthorized); - fastify.post( - '/payload', - { - schema: { - body: PayloadSchema, - }, - }, - async (request, reply) => { - if ( - (serverOpts.validate_chainhook_payloads ?? false) && - !compiledPayloadSchema.Check(request.body) - ) { - logger.error( - [...compiledPayloadSchema.Errors(request.body)], - `ChainhookEventObserver received an invalid payload` - ); - await reply.code(422).send(); - return; - } - try { - await callback(request.body.chainhook.uuid, request.body); - await reply.code(200).send(); - } catch (error) { - if (error instanceof BadPayloadRequestError) { - logger.error(error, `ChainhookEventObserver bad payload`); - await reply.code(400).send(); - } else { - logger.error(error, `ChainhookEventObserver error processing payload`); - await reply.code(500).send(); - } + fastify.post('/payload', async (request, reply) => { + if ( + (serverOpts.validate_chainhook_payloads ?? false) && + !compiledPayloadSchema.Check(request.body) + ) { + logger.error( + [...compiledPayloadSchema.Errors(request.body)], + `ChainhookEventObserver received an invalid payload` + ); + await reply.code(422).send(); + return; + } + const body = request.body as Payload; + try { + await callback(body.chainhook.uuid, body); + await reply.code(200).send(); + } catch (error) { + if (error instanceof BadPayloadRequestError) { + logger.error(error, `ChainhookEventObserver bad payload`); + await reply.code(400).send(); + } else { + logger.error(error, `ChainhookEventObserver error processing payload`); + await reply.code(500).send(); } } - ); + }); done(); };