use std::collections::{BTreeMap, HashSet};
use chainhook_types::{BitcoinNetwork, StacksNetwork};
use reqwest::Url;
use serde::ser::{SerializeSeq, Serializer};
use serde::{de, Deserialize, Deserializer, Serialize};
use schemars::JsonSchema;
use crate::utils::MAX_BLOCK_HEIGHTS_ENTRIES;
#[derive(Deserialize, Debug, Clone)]
pub struct ChainhookConfig {
pub stacks_chainhooks: Vec<StacksChainhookSpecification>,
pub bitcoin_chainhooks: Vec<BitcoinChainhookSpecification>,
}
impl ChainhookConfig {
pub fn new() -> ChainhookConfig {
ChainhookConfig {
stacks_chainhooks: vec![],
bitcoin_chainhooks: vec![],
}
}
pub fn register_full_specification(
&mut self,
networks: (&BitcoinNetwork, &StacksNetwork),
hook: ChainhookFullSpecification,
) -> Result<ChainhookSpecification, String> {
let spec = match hook {
ChainhookFullSpecification::Stacks(hook) => {
let spec = hook.into_selected_network_specification(networks.1)?;
self.stacks_chainhooks.push(spec.clone());
ChainhookSpecification::Stacks(spec)
}
ChainhookFullSpecification::Bitcoin(hook) => {
let spec = hook.into_selected_network_specification(networks.0)?;
self.bitcoin_chainhooks.push(spec.clone());
ChainhookSpecification::Bitcoin(spec)
}
};
Ok(spec)
}
pub fn enable_specification(&mut self, predicate_spec: &mut ChainhookSpecification) {
match predicate_spec {
ChainhookSpecification::Stacks(spec_to_enable) => {
for spec in self.stacks_chainhooks.iter_mut() {
if spec.uuid.eq(&spec_to_enable.uuid) {
spec.enabled = true;
spec_to_enable.enabled = true;
break;
}
}
}
ChainhookSpecification::Bitcoin(spec_to_enable) => {
for spec in self.bitcoin_chainhooks.iter_mut() {
if spec.uuid.eq(&spec_to_enable.uuid) {
spec.enabled = true;
spec_to_enable.enabled = true;
break;
}
}
}
};
}
pub fn register_specification(&mut self, spec: ChainhookSpecification) -> Result<(), String> {
match spec {
ChainhookSpecification::Stacks(spec) => {
let spec = spec.clone();
self.stacks_chainhooks.push(spec);
}
ChainhookSpecification::Bitcoin(spec) => {
let spec = spec.clone();
self.bitcoin_chainhooks.push(spec);
}
};
Ok(())
}
pub fn deregister_stacks_hook(
&mut self,
hook_uuid: String,
) -> Option<StacksChainhookSpecification> {
let mut i = 0;
while i < self.stacks_chainhooks.len() {
if self.stacks_chainhooks[i].uuid == hook_uuid {
let hook = self.stacks_chainhooks.remove(i);
return Some(hook);
} else {
i += 1;
}
}
None
}
pub fn deregister_bitcoin_hook(
&mut self,
hook_uuid: String,
) -> Option<BitcoinChainhookSpecification> {
let mut i = 0;
while i < self.bitcoin_chainhooks.len() {
if self.bitcoin_chainhooks[i].uuid == hook_uuid {
let hook = self.bitcoin_chainhooks.remove(i);
return Some(hook);
} else {
i += 1;
}
}
None
}
pub fn expire_stacks_hook(&mut self, hook_uuid: String, block_height: u64) {
let mut i = 0;
while i < self.stacks_chainhooks.len() {
if ChainhookSpecification::stacks_key(&self.stacks_chainhooks[i].uuid) == hook_uuid {
self.stacks_chainhooks[i].expired_at = Some(block_height);
break;
} else {
i += 1;
}
}
}
pub fn expire_bitcoin_hook(&mut self, hook_uuid: String, block_height: u64) {
let mut i = 0;
while i < self.bitcoin_chainhooks.len() {
if ChainhookSpecification::bitcoin_key(&self.bitcoin_chainhooks[i].uuid) == hook_uuid {
self.bitcoin_chainhooks[i].expired_at = Some(block_height);
break;
} else {
i += 1;
}
}
}
}
impl Serialize for ChainhookConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(
self.bitcoin_chainhooks.len() + self.stacks_chainhooks.len(),
))?;
for chainhook in self.bitcoin_chainhooks.iter() {
seq.serialize_element(chainhook)?;
}
for chainhook in self.stacks_chainhooks.iter() {
seq.serialize_element(chainhook)?;
}
seq.end()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChainhookSpecification {
Bitcoin(BitcoinChainhookSpecification),
Stacks(StacksChainhookSpecification),
}
impl ChainhookSpecification {
pub fn either_stx_or_btc_key(uuid: &str) -> String {
format!("predicate:{}", uuid)
}
pub fn stacks_key(uuid: &str) -> String {
format!("predicate:{}", uuid)
}
pub fn bitcoin_key(uuid: &str) -> String {
format!("predicate:{}", uuid)
}
pub fn key(&self) -> String {
match &self {
Self::Bitcoin(data) => Self::bitcoin_key(&data.uuid),
Self::Stacks(data) => Self::stacks_key(&data.uuid),
}
}
pub fn deserialize_specification(spec: &str) -> Result<ChainhookSpecification, String> {
let spec: ChainhookSpecification = serde_json::from_str(spec)
.map_err(|e| format!("unable to deserialize predicate {}", e.to_string()))?;
Ok(spec)
}
pub fn uuid(&self) -> &str {
match &self {
Self::Bitcoin(data) => &data.uuid,
Self::Stacks(data) => &data.uuid,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct BitcoinChainhookSpecification {
pub uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_uuid: Option<String>,
pub name: String,
pub network: BitcoinNetwork,
pub version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_after_occurrence: Option<u64>,
pub predicate: BitcoinPredicateType,
pub action: HookAction,
pub include_proof: bool,
pub include_inputs: bool,
pub include_outputs: bool,
pub include_witness: bool,
pub enabled: bool,
pub expired_at: Option<u64>,
}
impl BitcoinChainhookSpecification {
pub fn key(&self) -> String {
ChainhookSpecification::bitcoin_key(&self.uuid)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "chain")]
pub enum ChainhookFullSpecification {
Bitcoin(BitcoinChainhookFullSpecification),
Stacks(StacksChainhookFullSpecification),
}
impl ChainhookFullSpecification {
pub fn validate(&self) -> Result<(), String> {
match &self {
Self::Bitcoin(data) => {
for (_, spec) in data.networks.iter() {
let _ = spec.action.validate()?;
if let Some(end_block) = spec.end_block {
let start_block = spec.start_block.unwrap_or(0);
if start_block > end_block {
return Err(
"Chainhook specification field `end_block` should be greater than `start_block`."
.into(),
);
}
if (end_block - start_block) > MAX_BLOCK_HEIGHTS_ENTRIES {
return Err(format!("Chainhook specification exceeds max number of blocks to scan. Maximum: {}, Attempted: {}", MAX_BLOCK_HEIGHTS_ENTRIES, (end_block - start_block)));
}
}
}
}
Self::Stacks(data) => {
for (_, spec) in data.networks.iter() {
let _ = spec.action.validate()?;
if let Some(end_block) = spec.end_block {
let start_block = spec.start_block.unwrap_or(0);
if start_block > end_block {
return Err(
"Chainhook specification field `end_block` should be greater than `start_block`."
.into(),
);
}
if (end_block - start_block) > MAX_BLOCK_HEIGHTS_ENTRIES {
return Err(format!("Chainhook specification exceeds max number of blocks to scan. Maximum: {}, Attempted: {}", MAX_BLOCK_HEIGHTS_ENTRIES, (end_block - start_block)));
}
}
}
}
}
Ok(())
}
pub fn get_uuid(&self) -> &str {
match &self {
Self::Bitcoin(data) => &data.uuid,
Self::Stacks(data) => &data.uuid,
}
}
pub fn deserialize_specification(
spec: &str,
_key: &str,
) -> Result<ChainhookFullSpecification, String> {
let spec: ChainhookFullSpecification = serde_json::from_str(spec)
.map_err(|e| format!("unable to deserialize predicate {}", e.to_string()))?;
Ok(spec)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct BitcoinChainhookFullSpecification {
pub uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_uuid: Option<String>,
pub name: String,
pub version: u32,
pub networks: BTreeMap<BitcoinNetwork, BitcoinChainhookNetworkSpecification>,
}
impl BitcoinChainhookFullSpecification {
pub fn into_selected_network_specification(
mut self,
network: &BitcoinNetwork,
) -> Result<BitcoinChainhookSpecification, String> {
let spec = self
.networks
.remove(network)
.ok_or("Network unknown".to_string())?;
Ok(BitcoinChainhookSpecification {
uuid: self.uuid,
owner_uuid: self.owner_uuid,
name: self.name,
network: network.clone(),
version: self.version,
start_block: spec.start_block,
end_block: spec.end_block,
blocks: spec.blocks,
expire_after_occurrence: spec.expire_after_occurrence,
predicate: spec.predicate,
action: spec.action,
include_proof: spec.include_proof.unwrap_or(false),
include_inputs: spec.include_inputs.unwrap_or(false),
include_outputs: spec.include_outputs.unwrap_or(false),
include_witness: spec.include_witness.unwrap_or(false),
enabled: false,
expired_at: None,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct BitcoinChainhookNetworkSpecification {
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_after_occurrence: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_proof: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_inputs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_outputs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_witness: Option<bool>,
#[serde(rename = "if_this")]
pub predicate: BitcoinPredicateType,
#[serde(rename = "then_that")]
pub action: HookAction,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct StacksChainhookFullSpecification {
pub uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_uuid: Option<String>,
pub name: String,
pub version: u32,
pub networks: BTreeMap<StacksNetwork, StacksChainhookNetworkSpecification>,
}
impl StacksChainhookFullSpecification {
pub fn into_selected_network_specification(
mut self,
network: &StacksNetwork,
) -> Result<StacksChainhookSpecification, String> {
let spec = self
.networks
.remove(network)
.ok_or("Network unknown".to_string())?;
Ok(StacksChainhookSpecification {
uuid: self.uuid,
owner_uuid: self.owner_uuid,
name: self.name,
network: network.clone(),
version: self.version,
start_block: spec.start_block,
end_block: spec.end_block,
blocks: spec.blocks,
capture_all_events: spec.capture_all_events,
decode_clarity_values: spec.decode_clarity_values,
expire_after_occurrence: spec.expire_after_occurrence,
include_contract_abi: spec.include_contract_abi,
predicate: spec.predicate,
action: spec.action,
enabled: false,
expired_at: None,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct StacksChainhookNetworkSpecification {
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_after_occurrence: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capture_all_events: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decode_clarity_values: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_contract_abi: Option<bool>,
#[serde(rename = "if_this")]
pub predicate: StacksPredicate,
#[serde(rename = "then_that")]
pub action: HookAction,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HookAction {
HttpPost(HttpHook),
FileAppend(FileHook),
Noop,
}
impl HookAction {
pub fn validate(&self) -> Result<(), String> {
match &self {
HookAction::HttpPost(spec) => {
let _ = Url::parse(&spec.url)
.map_err(|e| format!("hook action url invalid ({})", e.to_string()))?;
}
HookAction::FileAppend(_) => {}
HookAction::Noop => {}
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct HttpHook {
pub url: String,
pub authorization_header: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FileHook {
pub path: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct ScriptTemplate {
pub instructions: Vec<ScriptInstruction>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ScriptInstruction {
Opcode(u8),
RawBytes(Vec<u8>),
Placeholder(String, u8),
}
impl ScriptTemplate {
pub fn parse(template: &str) -> Result<ScriptTemplate, String> {
let raw_instructions = template
.split_ascii_whitespace()
.map(|c| c.to_string())
.collect::<Vec<_>>();
let mut instructions = vec![];
for raw_instruction in raw_instructions.into_iter() {
if raw_instruction.starts_with("{") {
let placeholder = &raw_instruction[1..raw_instruction.len() - 1];
let (name, size) = match placeholder.split_once(":") {
Some(res) => res,
None => return Err(format!("malformed placeholder {}: should be {{placeholder-name:number-of-bytes}} (ex: {{id:4}}", raw_instruction))
};
let size = match size.parse::<u8>() {
Ok(res) => res,
Err(_) => return Err(format!("malformed placeholder {}: should be {{placeholder-name:number-of-bytes}} (ex: {{id:4}}", raw_instruction))
};
instructions.push(ScriptInstruction::Placeholder(name.to_string(), size));
} else if let Some(opcode) = opcode_to_hex(&raw_instruction) {
instructions.push(ScriptInstruction::Opcode(opcode));
} else if let Ok(bytes) = hex::decode(&raw_instruction) {
instructions.push(ScriptInstruction::RawBytes(bytes));
} else {
return Err(format!("unable to handle instruction {}", raw_instruction));
}
}
Ok(ScriptTemplate { instructions })
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct BitcoinTransactionFilterPredicate {
pub predicate: BitcoinPredicateType,
}
impl BitcoinTransactionFilterPredicate {
pub fn new(predicate: BitcoinPredicateType) -> BitcoinTransactionFilterPredicate {
BitcoinTransactionFilterPredicate { predicate }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "scope")]
pub enum BitcoinPredicateType {
Block,
Txid(ExactMatchingRule),
Inputs(InputPredicate),
Outputs(OutputPredicate),
StacksProtocol(StacksOperations),
OrdinalsProtocol(OrdinalOperations),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum InputPredicate {
Txid(TxinPredicate),
WitnessScript(MatchingRule),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum OutputPredicate {
OpReturn(MatchingRule),
P2pkh(ExactMatchingRule),
P2sh(ExactMatchingRule),
P2wpkh(ExactMatchingRule),
P2wsh(ExactMatchingRule),
Descriptor(DescriptorMatchingRule),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "operation")]
pub enum StacksOperations {
StackerRewarded,
BlockCommitted,
LeaderRegistered,
StxTransferred,
StxLocked,
}
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum OrdinalsMetaProtocol {
All,
#[serde(rename = "brc-20")]
Brc20,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct InscriptionFeedData {
#[serde(skip_serializing_if = "Option::is_none")]
pub meta_protocols: Option<HashSet<OrdinalsMetaProtocol>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "operation")]
pub enum OrdinalOperations {
InscriptionFeed(InscriptionFeedData),
}
pub fn get_stacks_canonical_magic_bytes(network: &BitcoinNetwork) -> [u8; 2] {
match network {
BitcoinNetwork::Mainnet => *b"X2",
BitcoinNetwork::Testnet => *b"T2",
BitcoinNetwork::Regtest => *b"id",
BitcoinNetwork::Signet => unreachable!(),
}
}
pub struct PoxConfig {
pub genesis_block_height: u64,
pub prepare_phase_len: u64,
pub reward_phase_len: u64,
pub rewarded_addresses_per_block: usize,
}
impl PoxConfig {
pub fn get_pox_cycle_len(&self) -> u64 {
self.prepare_phase_len + self.reward_phase_len
}
pub fn get_pox_cycle_id(&self, block_height: u64) -> u64 {
(block_height.saturating_sub(self.genesis_block_height)) / self.get_pox_cycle_len()
}
pub fn get_pos_in_pox_cycle(&self, block_height: u64) -> u64 {
(block_height.saturating_sub(self.genesis_block_height)) % self.get_pox_cycle_len()
}
pub fn get_burn_address(&self) -> &str {
match self.genesis_block_height {
666050 => "1111111111111111111114oLvT2",
2000000 => "burn-address-regtest",
_ => "burn-address",
}
}
}
const POX_CONFIG_MAINNET: PoxConfig = PoxConfig {
genesis_block_height: 666050,
prepare_phase_len: 100,
reward_phase_len: 2100,
rewarded_addresses_per_block: 2,
};
const POX_CONFIG_TESTNET: PoxConfig = PoxConfig {
genesis_block_height: 2000000,
prepare_phase_len: 50,
reward_phase_len: 1050,
rewarded_addresses_per_block: 2,
};
const POX_CONFIG_DEVNET: PoxConfig = PoxConfig {
genesis_block_height: 100,
prepare_phase_len: 4,
reward_phase_len: 10,
rewarded_addresses_per_block: 2,
};
pub fn get_canonical_pox_config(network: &BitcoinNetwork) -> PoxConfig {
match network {
BitcoinNetwork::Mainnet => POX_CONFIG_MAINNET,
BitcoinNetwork::Testnet => POX_CONFIG_TESTNET,
BitcoinNetwork::Regtest => POX_CONFIG_DEVNET,
BitcoinNetwork::Signet => unreachable!(),
}
}
#[derive(Debug, Clone, PartialEq)]
#[repr(u8)]
pub enum StacksOpcodes {
BlockCommit = '[' as u8,
KeyRegister = '^' as u8,
StackStx = 'x' as u8,
PreStx = 'p' as u8,
TransferStx = '$' as u8,
}
impl TryFrom<u8> for StacksOpcodes {
type Error = ();
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
x if x == StacksOpcodes::BlockCommit as u8 => Ok(StacksOpcodes::BlockCommit),
x if x == StacksOpcodes::KeyRegister as u8 => Ok(StacksOpcodes::KeyRegister),
x if x == StacksOpcodes::StackStx as u8 => Ok(StacksOpcodes::StackStx),
x if x == StacksOpcodes::PreStx as u8 => Ok(StacksOpcodes::PreStx),
x if x == StacksOpcodes::TransferStx as u8 => Ok(StacksOpcodes::TransferStx),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct TxinPredicate {
pub txid: String,
pub vout: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BlockIdentifierIndexRule {
Equals(u64),
HigherThan(u64),
LowerThan(u64),
Between(u64, u64),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Scope {
Inputs,
Outputs,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum MatchingRule {
Equals(String),
StartsWith(String),
EndsWith(String),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExactMatchingRule {
Equals(String),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct DescriptorMatchingRule {
pub expression: String,
#[serde(default, deserialize_with = "deserialize_descriptor_range")]
pub range: Option<[u32; 2]>,
}
fn deserialize_descriptor_range<'de, D>(deserializer: D) -> Result<Option<[u32; 2]>, D::Error>
where
D: Deserializer<'de>,
{
let range: [u32; 2] = Deserialize::deserialize(deserializer)?;
if !(range[0] < range[1]) {
Err(de::Error::custom(
"First element of 'range' must be lower than the second element",
))
} else {
Ok(Some(range))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BlockIdentifierHashRule {
Equals(String),
BuildsOff(String),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct StacksChainhookSpecification {
pub uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_uuid: Option<String>,
pub name: String,
pub network: StacksNetwork,
pub version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_after_occurrence: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capture_all_events: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decode_clarity_values: Option<bool>,
pub include_contract_abi: Option<bool>,
#[serde(rename = "predicate")]
pub predicate: StacksPredicate,
pub action: HookAction,
pub enabled: bool,
pub expired_at: Option<u64>,
}
impl StacksChainhookSpecification {
pub fn key(&self) -> String {
ChainhookSpecification::stacks_key(&self.uuid)
}
pub fn is_predicate_targeting_block_header(&self) -> bool {
match &self.predicate {
StacksPredicate::BlockHeight(_)
=> true,
_ => false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "scope")]
pub enum StacksPredicate {
BlockHeight(BlockIdentifierIndexRule),
ContractDeployment(StacksContractDeploymentPredicate),
ContractCall(StacksContractCallBasedPredicate),
PrintEvent(StacksPrintEventBasedPredicate),
FtEvent(StacksFtEventBasedPredicate),
NftEvent(StacksNftEventBasedPredicate),
StxEvent(StacksStxEventBasedPredicate),
Txid(ExactMatchingRule),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct StacksContractCallBasedPredicate {
pub contract_identifier: String,
pub method: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum StacksContractDeploymentPredicate {
Deployer(String),
ImplementTrait(StacksTrait),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum StacksTrait {
Sip09,
Sip10,
#[serde(rename = "*")]
Any,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
pub enum StacksPrintEventBasedPredicate {
Contains {
contract_identifier: String,
contains: String,
},
MatchesRegex {
contract_identifier: String,
#[serde(rename = "matches_regex")]
regex: String,
},
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct StacksFtEventBasedPredicate {
pub asset_identifier: String,
pub actions: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct StacksNftEventBasedPredicate {
pub asset_identifier: String,
pub actions: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct StacksStxEventBasedPredicate {
pub actions: Vec<String>,
}
pub fn opcode_to_hex(asm: &str) -> Option<u8> {
match asm {
"OP_PUSHBYTES_0" => Some(0x00),
"OP_PUSHBYTES_1" => Some(0x01),
"OP_PUSHBYTES_2" => Some(0x02),
"OP_PUSHBYTES_3" => Some(0x03),
"OP_PUSHBYTES_4" => Some(0x04),
"OP_PUSHBYTES_5" => Some(0x05),
"OP_PUSHBYTES_6" => Some(0x06),
"OP_PUSHBYTES_7" => Some(0x07),
"OP_PUSHBYTES_8" => Some(0x08),
"OP_PUSHBYTES_9" => Some(0x09),
"OP_PUSHBYTES_10" => Some(0x0a),
"OP_PUSHBYTES_11" => Some(0x0b),
"OP_PUSHBYTES_12" => Some(0x0c),
"OP_PUSHBYTES_13" => Some(0x0d),
"OP_PUSHBYTES_14" => Some(0x0e),
"OP_PUSHBYTES_15" => Some(0x0f),
"OP_PUSHBYTES_16" => Some(0x10),
"OP_PUSHBYTES_17" => Some(0x11),
"OP_PUSHBYTES_18" => Some(0x12),
"OP_PUSHBYTES_19" => Some(0x13),
"OP_PUSHBYTES_20" => Some(0x14),
"OP_PUSHBYTES_21" => Some(0x15),
"OP_PUSHBYTES_22" => Some(0x16),
"OP_PUSHBYTES_23" => Some(0x17),
"OP_PUSHBYTES_24" => Some(0x18),
"OP_PUSHBYTES_25" => Some(0x19),
"OP_PUSHBYTES_26" => Some(0x1a),
"OP_PUSHBYTES_27" => Some(0x1b),
"OP_PUSHBYTES_28" => Some(0x1c),
"OP_PUSHBYTES_29" => Some(0x1d),
"OP_PUSHBYTES_30" => Some(0x1e),
"OP_PUSHBYTES_31" => Some(0x1f),
"OP_PUSHBYTES_32" => Some(0x20),
"OP_PUSHBYTES_33" => Some(0x21),
"OP_PUSHBYTES_34" => Some(0x22),
"OP_PUSHBYTES_35" => Some(0x23),
"OP_PUSHBYTES_36" => Some(0x24),
"OP_PUSHBYTES_37" => Some(0x25),
"OP_PUSHBYTES_38" => Some(0x26),
"OP_PUSHBYTES_39" => Some(0x27),
"OP_PUSHBYTES_40" => Some(0x28),
"OP_PUSHBYTES_41" => Some(0x29),
"OP_PUSHBYTES_42" => Some(0x2a),
"OP_PUSHBYTES_43" => Some(0x2b),
"OP_PUSHBYTES_44" => Some(0x2c),
"OP_PUSHBYTES_45" => Some(0x2d),
"OP_PUSHBYTES_46" => Some(0x2e),
"OP_PUSHBYTES_47" => Some(0x2f),
"OP_PUSHBYTES_48" => Some(0x30),
"OP_PUSHBYTES_49" => Some(0x31),
"OP_PUSHBYTES_50" => Some(0x32),
"OP_PUSHBYTES_51" => Some(0x33),
"OP_PUSHBYTES_52" => Some(0x34),
"OP_PUSHBYTES_53" => Some(0x35),
"OP_PUSHBYTES_54" => Some(0x36),
"OP_PUSHBYTES_55" => Some(0x37),
"OP_PUSHBYTES_56" => Some(0x38),
"OP_PUSHBYTES_57" => Some(0x39),
"OP_PUSHBYTES_58" => Some(0x3a),
"OP_PUSHBYTES_59" => Some(0x3b),
"OP_PUSHBYTES_60" => Some(0x3c),
"OP_PUSHBYTES_61" => Some(0x3d),
"OP_PUSHBYTES_62" => Some(0x3e),
"OP_PUSHBYTES_63" => Some(0x3f),
"OP_PUSHBYTES_64" => Some(0x40),
"OP_PUSHBYTES_65" => Some(0x41),
"OP_PUSHBYTES_66" => Some(0x42),
"OP_PUSHBYTES_67" => Some(0x43),
"OP_PUSHBYTES_68" => Some(0x44),
"OP_PUSHBYTES_69" => Some(0x45),
"OP_PUSHBYTES_70" => Some(0x46),
"OP_PUSHBYTES_71" => Some(0x47),
"OP_PUSHBYTES_72" => Some(0x48),
"OP_PUSHBYTES_73" => Some(0x49),
"OP_PUSHBYTES_74" => Some(0x4a),
"OP_PUSHBYTES_75" => Some(0x4b),
"OP_PUSHDATA1" => Some(0x4c),
"OP_PUSHDATA2" => Some(0x4d),
"OP_PUSHDATA4" => Some(0x4e),
"OP_PUSHNUM_NEG1" => Some(0x4f),
"OP_RESERVED" => Some(0x50),
"OP_PUSHNUM_1" => Some(0x51),
"OP_PUSHNUM_2" => Some(0x52),
"OP_PUSHNUM_3" => Some(0x53),
"OP_PUSHNUM_4" => Some(0x54),
"OP_PUSHNUM_5" => Some(0x55),
"OP_PUSHNUM_6" => Some(0x56),
"OP_PUSHNUM_7" => Some(0x57),
"OP_PUSHNUM_8" => Some(0x58),
"OP_PUSHNUM_9" => Some(0x59),
"OP_PUSHNUM_10" => Some(0x5a),
"OP_PUSHNUM_11" => Some(0x5b),
"OP_PUSHNUM_12" => Some(0x5c),
"OP_PUSHNUM_13" => Some(0x5d),
"OP_PUSHNUM_14" => Some(0x5e),
"OP_PUSHNUM_15" => Some(0x5f),
"OP_PUSHNUM_16" => Some(0x60),
"OP_NOP" => Some(0x61),
"OP_VER" => Some(0x62),
"OP_IF" => Some(0x63),
"OP_NOTIF" => Some(0x64),
"OP_VERIF" => Some(0x65),
"OP_VERNOTIF" => Some(0x66),
"OP_ELSE" => Some(0x67),
"OP_ENDIF" => Some(0x68),
"OP_VERIFY" => Some(0x69),
"OP_RETURN" => Some(0x6a),
"OP_TOALTSTACK" => Some(0x6b),
"OP_FROMALTSTACK" => Some(0x6c),
"OP_2DROP" => Some(0x6d),
"OP_2DUP" => Some(0x6e),
"OP_3DUP" => Some(0x6f),
"OP_2OVER" => Some(0x70),
"OP_2ROT" => Some(0x71),
"OP_2SWAP" => Some(0x72),
"OP_IFDUP" => Some(0x73),
"OP_DEPTH" => Some(0x74),
"OP_DROP" => Some(0x75),
"OP_DUP" => Some(0x76),
"OP_NIP" => Some(0x77),
"OP_OVER" => Some(0x78),
"OP_PICK" => Some(0x79),
"OP_ROLL" => Some(0x7a),
"OP_ROT" => Some(0x7b),
"OP_SWAP" => Some(0x7c),
"OP_TUCK" => Some(0x7d),
"OP_CAT" => Some(0x7e),
"OP_SUBSTR" => Some(0x7f),
"OP_LEFT" => Some(0x80),
"OP_RIGHT" => Some(0x81),
"OP_SIZE" => Some(0x82),
"OP_INVERT" => Some(0x83),
"OP_AND" => Some(0x84),
"OP_OR" => Some(0x85),
"OP_XOR" => Some(0x86),
"OP_EQUAL" => Some(0x87),
"OP_EQUALVERIFY" => Some(0x88),
"OP_RESERVED1" => Some(0x89),
"OP_RESERVED2" => Some(0x8a),
"OP_1ADD" => Some(0x8b),
"OP_1SUB" => Some(0x8c),
"OP_2MUL" => Some(0x8d),
"OP_2DIV" => Some(0x8e),
"OP_NEGATE" => Some(0x8f),
"OP_ABS" => Some(0x90),
"OP_NOT" => Some(0x91),
"OP_0NOTEQUAL" => Some(0x92),
"OP_ADD" => Some(0x93),
"OP_SUB" => Some(0x94),
"OP_MUL" => Some(0x95),
"OP_DIV" => Some(0x96),
"OP_MOD" => Some(0x97),
"OP_LSHIFT" => Some(0x98),
"OP_RSHIFT" => Some(0x99),
"OP_BOOLAND" => Some(0x9a),
"OP_BOOLOR" => Some(0x9b),
"OP_NUMEQUAL" => Some(0x9c),
"OP_NUMEQUALVERIFY" => Some(0x9d),
"OP_NUMNOTEQUAL" => Some(0x9e),
"OP_LESSTHAN" => Some(0x9f),
"OP_GREATERTHAN" => Some(0xa0),
"OP_LESSTHANOREQUAL" => Some(0xa1),
"OP_GREATERTHANOREQUAL" => Some(0xa2),
"OP_MIN" => Some(0xa3),
"OP_MAX" => Some(0xa4),
"OP_WITHIN" => Some(0xa5),
"OP_RIPEMD160" => Some(0xa6),
"OP_SHA1" => Some(0xa7),
"OP_SHA256" => Some(0xa8),
"OP_HASH160" => Some(0xa9),
"OP_HASH256" => Some(0xaa),
"OP_CODESEPARATOR" => Some(0xab),
"OP_CHECKSIG" => Some(0xac),
"OP_CHECKSIGVERIFY" => Some(0xad),
"OP_CHECKMULTISIG" => Some(0xae),
"OP_CHECKMULTISIGVERIFY" => Some(0xaf),
"OP_NOP1" => Some(0xb0),
"OP_CLTV" => Some(0xb1),
"OP_CSV" => Some(0xb2),
"OP_NOP4" => Some(0xb3),
"OP_NOP5" => Some(0xb4),
"OP_NOP6" => Some(0xb5),
"OP_NOP7" => Some(0xb6),
"OP_NOP8" => Some(0xb7),
"OP_NOP9" => Some(0xb8),
"OP_NOP10" => Some(0xb9),
"OP_RETURN_186" => Some(0xba),
"OP_RETURN_187" => Some(0xbb),
"OP_RETURN_188" => Some(0xbc),
"OP_RETURN_189" => Some(0xbd),
"OP_RETURN_190" => Some(0xbe),
"OP_RETURN_191" => Some(0xbf),
"OP_RETURN_192" => Some(0xc0),
"OP_RETURN_193" => Some(0xc1),
"OP_RETURN_194" => Some(0xc2),
"OP_RETURN_195" => Some(0xc3),
"OP_RETURN_196" => Some(0xc4),
"OP_RETURN_197" => Some(0xc5),
"OP_RETURN_198" => Some(0xc6),
"OP_RETURN_199" => Some(0xc7),
"OP_RETURN_200" => Some(0xc8),
"OP_RETURN_201" => Some(0xc9),
"OP_RETURN_202" => Some(0xca),
"OP_RETURN_203" => Some(0xcb),
"OP_RETURN_204" => Some(0xcc),
"OP_RETURN_205" => Some(0xcd),
"OP_RETURN_206" => Some(0xce),
"OP_RETURN_207" => Some(0xcf),
"OP_RETURN_208" => Some(0xd0),
"OP_RETURN_209" => Some(0xd1),
"OP_RETURN_210" => Some(0xd2),
"OP_RETURN_211" => Some(0xd3),
"OP_RETURN_212" => Some(0xd4),
"OP_RETURN_213" => Some(0xd5),
"OP_RETURN_214" => Some(0xd6),
"OP_RETURN_215" => Some(0xd7),
"OP_RETURN_216" => Some(0xd8),
"OP_RETURN_217" => Some(0xd9),
"OP_RETURN_218" => Some(0xda),
"OP_RETURN_219" => Some(0xdb),
"OP_RETURN_220" => Some(0xdc),
"OP_RETURN_221" => Some(0xdd),
"OP_RETURN_222" => Some(0xde),
"OP_RETURN_223" => Some(0xdf),
"OP_RETURN_224" => Some(0xe0),
"OP_RETURN_225" => Some(0xe1),
"OP_RETURN_226" => Some(0xe2),
"OP_RETURN_227" => Some(0xe3),
"OP_RETURN_228" => Some(0xe4),
"OP_RETURN_229" => Some(0xe5),
"OP_RETURN_230" => Some(0xe6),
"OP_RETURN_231" => Some(0xe7),
"OP_RETURN_232" => Some(0xe8),
"OP_RETURN_233" => Some(0xe9),
"OP_RETURN_234" => Some(0xea),
"OP_RETURN_235" => Some(0xeb),
"OP_RETURN_236" => Some(0xec),
"OP_RETURN_237" => Some(0xed),
"OP_RETURN_238" => Some(0xee),
"OP_RETURN_239" => Some(0xef),
"OP_RETURN_240" => Some(0xf0),
"OP_RETURN_241" => Some(0xf1),
"OP_RETURN_242" => Some(0xf2),
"OP_RETURN_243" => Some(0xf3),
"OP_RETURN_244" => Some(0xf4),
"OP_RETURN_245" => Some(0xf5),
"OP_RETURN_246" => Some(0xf6),
"OP_RETURN_247" => Some(0xf7),
"OP_RETURN_248" => Some(0xf8),
"OP_RETURN_249" => Some(0xf9),
"OP_RETURN_250" => Some(0xfa),
"OP_RETURN_251" => Some(0xfb),
"OP_RETURN_252" => Some(0xfc),
"OP_RETURN_253" => Some(0xfd),
"OP_RETURN_254" => Some(0xfe),
"OP_RETURN_255" => Some(0xff),
_ => None,
}
}