diff --git a/src/chain.rs b/src/chain.rs index 230687d29b..34d3c128ca 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -52,7 +52,7 @@ impl Chain { Self::Mainnet => 779832, Self::Regtest => 0, Self::Signet => 0, - Self::Testnet => 0, + Self::Testnet => 2413343, } } diff --git a/src/index.rs b/src/index.rs index 086ec34207..29d00ed454 100644 --- a/src/index.rs +++ b/src/index.rs @@ -425,8 +425,8 @@ impl Index { }) } - pub(crate) fn get_chain_network(&self) -> Network { - self.options.chain().network() + pub(crate) fn get_chain(&self) -> Chain { + self.options.chain() } #[cfg(test)] diff --git a/src/index/extend.rs b/src/index/extend.rs index 9c5779d149..105ce474fc 100644 --- a/src/index/extend.rs +++ b/src/index/extend.rs @@ -43,10 +43,10 @@ impl Index { txid: Txid, rtx: &Rtx, client: &Client, - network: Network, + chain: Chain, index_transactions: bool, ) -> Result> { - let genesis_block = bitcoin::blockdata::constants::genesis_block(network); + let genesis_block = chain.genesis_block(); let genesis_block_coinbase_transaction = genesis_block.coinbase().unwrap(); if txid == genesis_block_coinbase_transaction.txid() { @@ -248,7 +248,7 @@ impl Index { rtx: &Rtx, client: &Client, outpoint: OutPoint, - network: Network, + chain: Chain, index_transactions: bool, ) -> Result> { // Try to get the txout from the database store at first. @@ -257,13 +257,14 @@ impl Index { } else { // Try to get the txout from the transaction table or the RPC request. Ok( - Self::get_transaction_with_rtx(outpoint.txid, rtx, client, network, index_transactions)? - .map(|tx| { + Self::get_transaction_with_rtx(outpoint.txid, rtx, client, chain, index_transactions)?.map( + |tx| { tx.output .get(usize::try_from(outpoint.vout).unwrap()) .unwrap() .to_owned() - }), + }, + ), ) } } diff --git a/src/index/updater.rs b/src/index/updater.rs index 600e0262d0..fd0eb18b73 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -1,4 +1,4 @@ -use crate::okx::protocol::{context::Context, BlockContext, ProtocolConfig, ProtocolManager}; +use crate::okx::protocol::{context::Context, ChainContext, ProtocolConfig, ProtocolManager}; use std::sync::atomic::{AtomicUsize, Ordering}; use { self::{inscription_updater::InscriptionUpdater, rune_updater::RuneUpdater}, @@ -621,8 +621,8 @@ impl<'index> Updater<'_> { inscription_updater.flush_cache()?; let mut context = Context { - chain: BlockContext { - network: index.get_chain_network(), + chain_conf: ChainContext { + chain: self.index.options.chain(), blockheight: self.height, blocktime: block.header.time, }, diff --git a/src/okx/datastore/brc20/errors.rs b/src/okx/datastore/brc20/errors.rs index 4da0911610..db3f81e4fd 100644 --- a/src/okx/datastore/brc20/errors.rs +++ b/src/okx/datastore/brc20/errors.rs @@ -48,6 +48,15 @@ pub enum BRC20Error { #[error("transferable owner not match {0}")] TransferableOwnerNotMatch(InscriptionId), + #[error("self issuance not activated")] + SelfIssuanceNotActivated, + + #[error("'self_mint' must be set to 'true', when deploying 5 bytes tick")] + SelfIssuanceCheckedFailed, + + #[error("self mint permission denied")] + SelfMintPermissionDenied, + /// an InternalError is an error that happens exceed our expect /// and should not happen under normal circumstances #[error("internal error: {0}")] diff --git a/src/okx/datastore/brc20/events.rs b/src/okx/datastore/brc20/events.rs index 327da37bb0..7ed57e52a3 100644 --- a/src/okx/datastore/brc20/events.rs +++ b/src/okx/datastore/brc20/events.rs @@ -36,6 +36,7 @@ pub struct DeployEvent { pub limit_per_mint: u128, pub decimal: u8, pub tick: Tick, + pub self_mint: bool, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] diff --git a/src/okx/datastore/brc20/mod.rs b/src/okx/datastore/brc20/mod.rs index 411e28de2d..04f9a17097 100644 --- a/src/okx/datastore/brc20/mod.rs +++ b/src/okx/datastore/brc20/mod.rs @@ -65,6 +65,8 @@ pub trait Brc20ReaderWriter: Brc20Reader { minted_block_number: u32, ) -> Result<(), Self::Error>; + fn update_burned_token_info(&mut self, tick: &Tick, burned_amt: u128) -> Result<(), Self::Error>; + fn save_transaction_receipts( &mut self, txid: &Txid, diff --git a/src/okx/datastore/brc20/redb/table.rs b/src/okx/datastore/brc20/redb/table.rs index d4a23cff6b..c3902d046c 100644 --- a/src/okx/datastore/brc20/redb/table.rs +++ b/src/okx/datastore/brc20/redb/table.rs @@ -236,6 +236,21 @@ pub fn update_mint_token_info( Ok(()) } +pub fn update_burned_token_info( + table: &mut Table<'_, '_, &'static str, &'static [u8]>, + tick: &Tick, + burned_amt: u128, +) -> Result<()> { + let mut info = + get_token_info(table, tick)?.unwrap_or_else(|| panic!("token {} not exist", tick.as_str())); + info.burned_supply = burned_amt; + table.insert( + tick.to_lowercase().hex().as_str(), + rmp_serde::to_vec(&info).unwrap().as_slice(), + )?; + Ok(()) +} + // BRC20_EVENTS pub fn save_transaction_receipts( table: &mut Table<'_, '_, &'static TxidValue, &'static [u8]>, diff --git a/src/okx/datastore/brc20/tick.rs b/src/okx/datastore/brc20/tick.rs index ed6533f055..66a7177ccd 100644 --- a/src/okx/datastore/brc20/tick.rs +++ b/src/okx/datastore/brc20/tick.rs @@ -2,10 +2,12 @@ use super::*; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt::Formatter, str::FromStr}; -pub const TICK_BYTE_COUNT: usize = 4; +pub const ORIGINAL_TICK_LENGTH: usize = 4; +pub const SELF_ISSUANCE_TICK_LENGTH: usize = 5; +pub const MAX_TICK_BYTE_COUNT: usize = SELF_ISSUANCE_TICK_LENGTH; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Tick([u8; TICK_BYTE_COUNT]); +pub struct Tick(Box<[u8]>); impl FromStr for Tick { type Err = BRC20Error; @@ -13,24 +15,28 @@ impl FromStr for Tick { fn from_str(s: &str) -> Result { let bytes = s.as_bytes(); - if bytes.len() != TICK_BYTE_COUNT { + if bytes.len() < ORIGINAL_TICK_LENGTH || bytes.len() > SELF_ISSUANCE_TICK_LENGTH { return Err(BRC20Error::InvalidTickLen(s.to_string())); } - Ok(Self(bytes.try_into().unwrap())) + Ok(Self(bytes.into())) } } impl Tick { - pub fn as_str(&self) -> &str { + pub fn as_str(&self) -> String { // NOTE: Tick comes from &str by from_str, // so it could be calling unwrap when convert to str - std::str::from_utf8(self.0.as_slice()).unwrap() + String::from_utf8(self.0.to_vec()).unwrap() } pub fn to_lowercase(&self) -> LowerTick { LowerTick::new(&self.as_str().to_lowercase()) } + + pub fn self_issuance_tick(&self) -> bool { + self.0.len() == SELF_ISSUANCE_TICK_LENGTH + } } impl Serialize for Tick { @@ -71,17 +77,17 @@ impl LowerTick { } pub fn hex(&self) -> String { - let mut data = [0u8; TICK_BYTE_COUNT * 4]; + let mut data = [0u8; MAX_TICK_BYTE_COUNT * 4]; data[..self.0.len()].copy_from_slice(&self.0); hex::encode(data) } pub fn min_hex() -> String { - hex::encode([0u8; TICK_BYTE_COUNT * 4]) + hex::encode([0u8; MAX_TICK_BYTE_COUNT * 4]) } pub fn max_hex() -> String { - hex::encode([0xffu8; TICK_BYTE_COUNT * 4]) + hex::encode([0xffu8; MAX_TICK_BYTE_COUNT * 4]) } } @@ -106,22 +112,22 @@ mod tests { assert!(Tick::from_str("aBc1").is_ok()); assert!("aBc1".parse::().is_ok()); assert!("ατ".parse::().is_ok()); - assert!("∑ii".parse::().is_err()); + assert!("∑ii".parse::().is_ok()); // when self issuance is enabled assert!("∑i".parse::().is_ok()); assert!("⊢i".parse::().is_ok()); - assert!("⊢ii".parse::().is_err()); + assert!("⊢ii".parse::().is_ok()); // when self issuance is enabled assert!("≯a".parse::().is_ok()); - assert!("a≯a".parse::().is_err()); + assert!("a≯a".parse::().is_ok()); // when self issuance is enabled } #[test] fn test_tick_hex() { assert_eq!( Tick::from_str("XAİ").unwrap().to_lowercase().hex(), - "786169cc870000000000000000000000" + "786169cc87000000000000000000000000000000" ); assert_eq!( Tick::from_str("aBc1").unwrap().to_lowercase().hex(), - "61626331000000000000000000000000" + "6162633100000000000000000000000000000000" ); } diff --git a/src/okx/datastore/brc20/token_info.rs b/src/okx/datastore/brc20/token_info.rs index bce89a90f3..57fe3a0eef 100644 --- a/src/okx/datastore/brc20/token_info.rs +++ b/src/okx/datastore/brc20/token_info.rs @@ -7,10 +7,12 @@ pub struct TokenInfo { pub inscription_id: InscriptionId, pub inscription_number: i32, pub supply: u128, + pub burned_supply: u128, pub minted: u128, pub limit_per_mint: u128, pub decimal: u8, pub deploy_by: ScriptKey, + pub is_self_mint: bool, pub deployed_number: u32, pub deployed_timestamp: u32, pub latest_mint_number: u32, diff --git a/src/okx/datastore/ord/mod.rs b/src/okx/datastore/ord/mod.rs index 6c5f065636..3fe3b28afb 100644 --- a/src/okx/datastore/ord/mod.rs +++ b/src/okx/datastore/ord/mod.rs @@ -1,10 +1,7 @@ pub use self::operation::{Action, InscriptionOp}; -use bitcoin::Network; -use crate::okx::datastore::ScriptKey; -use crate::SatPoint; use { - crate::{InscriptionId, Result}, + crate::{okx::datastore::ScriptKey, Chain, InscriptionId, Result, SatPoint}, bitcoin::Txid, collections::CollectionKind, std::fmt::{Debug, Display}, @@ -25,7 +22,7 @@ pub trait OrdReader { fn get_script_key_on_satpoint( &mut self, satpoint: &SatPoint, - network: Network, + chain: Chain, ) -> Result; fn get_transaction_operations( diff --git a/src/okx/datastore/script_key.rs b/src/okx/datastore/script_key.rs index 3dfe564d56..bb771b2d85 100644 --- a/src/okx/datastore/script_key.rs +++ b/src/okx/datastore/script_key.rs @@ -1,11 +1,15 @@ -use bitcoin::{address, Address, Network, Script, ScriptHash}; +use crate::Chain; +use bitcoin::{address, Address, Script, ScriptHash}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub enum ScriptKey { Address(Address), - ScriptHash(ScriptHash), + ScriptHash { + script_hash: ScriptHash, + is_op_return: bool, + }, } impl ScriptKey { @@ -13,11 +17,14 @@ impl ScriptKey { pub fn from_address(address: Address) -> Self { ScriptKey::Address(Address::new(address.network, address.payload)) } - pub fn from_script(script: &Script, network: Network) -> Self { - match Address::from_script(script, network) { - Ok(address) => ScriptKey::Address(Address::new(address.network, address.payload)), - Err(_) => ScriptKey::ScriptHash(script.script_hash()), - } + pub fn from_script(script: &Script, chain: Chain) -> Self { + chain + .address_from_script(script) + .map(|address| Self::Address(Address::new(address.network, address.payload))) + .unwrap_or(ScriptKey::ScriptHash { + script_hash: script.script_hash(), + is_op_return: script.is_op_return(), + }) } } @@ -28,7 +35,7 @@ impl Display for ScriptKey { "{}", match self { ScriptKey::Address(address) => address.clone().assume_checked().to_string(), - ScriptKey::ScriptHash(script_hash) => script_hash.to_string(), + ScriptKey::ScriptHash { script_hash, .. } => script_hash.to_string(), } ) } @@ -57,7 +64,7 @@ mod tests { .payload .script_pubkey(); assert_eq!( - ScriptKey::from_script(&script, Network::Bitcoin), + ScriptKey::from_script(&script, Chain::Mainnet), ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap()) ); let binding = hex::decode( @@ -66,49 +73,11 @@ mod tests { .unwrap(); let script = Script::from_bytes(binding.as_slice()); assert_eq!( - ScriptKey::from_script(script, Network::Bitcoin), - ScriptKey::ScriptHash( - ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap() - ) - ); - } - #[test] - fn test_script_key_serialize() { - let script_key = - ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap()); - assert_eq!( - serde_json::to_string(&script_key).unwrap(), - r#"{"Address":"bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4"}"# - ); - let script_key = ScriptKey::ScriptHash( - ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(), - ); - assert_eq!( - serde_json::to_string(&script_key).unwrap(), - r#"{"ScriptHash":"df65c8a338dce7900824e7bd18c336656ca19e57"}"# - ); - } - - #[test] - fn test_script_key_deserialize() { - let script_key = - ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap()); - assert_eq!( - script_key, - serde_json::from_str::( - r#"{"Address":"bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4"}"# - ) - .unwrap() - ); - let script_key = ScriptKey::ScriptHash( - ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(), - ); - assert_eq!( - serde_json::from_str::( - r#"{"ScriptHash":"df65c8a338dce7900824e7bd18c336656ca19e57"}"# - ) - .unwrap(), - script_key + ScriptKey::from_script(script, Chain::Mainnet), + ScriptKey::ScriptHash { + script_hash: ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(), + is_op_return: false, + }, ); } } diff --git a/src/okx/protocol/brc20/mod.rs b/src/okx/protocol/brc20/mod.rs index 1104403276..898c48a332 100644 --- a/src/okx/protocol/brc20/mod.rs +++ b/src/okx/protocol/brc20/mod.rs @@ -13,6 +13,7 @@ mod msg_resolver; mod num; mod operation; mod params; +mod policies; use self::error::Error; pub(crate) use self::{ diff --git a/src/okx/protocol/brc20/msg_executor.rs b/src/okx/protocol/brc20/msg_executor.rs index b37adc5114..4096b5197c 100644 --- a/src/okx/protocol/brc20/msg_executor.rs +++ b/src/okx/protocol/brc20/msg_executor.rs @@ -16,11 +16,10 @@ use crate::{ context::Context, }, }, - Result, + Chain, Result, }; use anyhow::anyhow; use bigdecimal::num_bigint::Sign; -use bitcoin::Network; use std::str::FromStr; #[derive(Debug, Clone, PartialEq)] @@ -36,7 +35,7 @@ pub struct ExecutionMessage { } impl ExecutionMessage { - pub fn from_message(context: &mut Context, msg: &Message, network: Network) -> Result { + pub fn from_message(context: &mut Context, msg: &Message, chain: Chain) -> Result { Ok(Self { txid: msg.txid, inscription_id: msg.inscription_id, @@ -45,9 +44,9 @@ impl ExecutionMessage { new_satpoint: msg .new_satpoint .ok_or(anyhow!("new satpoint cannot be None"))?, - from: context.get_script_key_on_satpoint(&msg.old_satpoint, network)?, + from: context.get_script_key_on_satpoint(&msg.old_satpoint, chain)?, to: if msg.sat_in_outputs { - Some(context.get_script_key_on_satpoint(msg.new_satpoint.as_ref().unwrap(), network)?) + Some(context.get_script_key_on_satpoint(msg.new_satpoint.as_ref().unwrap(), chain)?) } else { None }, @@ -60,7 +59,7 @@ pub fn execute(context: &mut Context, msg: &ExecutionMessage) -> Result log::debug!("BRC20 execute message: {:?}", msg); let event = match &msg.op { Operation::Deploy(deploy) => process_deploy(context, msg, deploy.clone()), - Operation::Mint(mint) => process_mint(context, msg, mint.clone()), + Operation::Mint { mint, parent } => process_mint(context, msg, mint.clone(), *parent), Operation::InscribeTransfer(transfer) => { process_inscribe_transfer(context, msg, transfer.clone()) } @@ -96,6 +95,25 @@ fn process_deploy( let to_script_key = msg.to.clone().ok_or(BRC20Error::InscribeToCoinbase)?; let tick = deploy.tick.parse::()?; + let mut max_supply = deploy.max_supply.clone(); + let mut is_self_mint = false; + + // proposal for issuance self mint token. + // https://l1f.discourse.group/t/brc-20-proposal-for-issuance-and-burn-enhancements-brc20-ip-1/621 + if tick.self_issuance_tick() { + if context.chain_conf.blockheight + < policies::HardForks::self_issuance_activation_height(context.chain_conf.chain) + { + return Err(Error::BRC20Error(BRC20Error::SelfIssuanceNotActivated)); + } + if !deploy.self_mint.unwrap_or_default() { + return Err(Error::BRC20Error(BRC20Error::SelfIssuanceCheckedFailed)); + } + if deploy.max_supply == u64::MIN.to_string() { + max_supply = u64::MAX.to_string(); + } + is_self_mint = true; + } if let Some(stored_tick_info) = context.get_token_info(&tick).map_err(Error::LedgerError)? { return Err(Error::BRC20Error(BRC20Error::DuplicateTick( @@ -110,7 +128,7 @@ fn process_deploy( } let base = BIGDECIMAL_TEN.checked_powu(u64::from(dec))?; - let supply = Num::from_str(&deploy.max_supply)?; + let supply = Num::from_str(&max_supply)?; if supply.sign() == Sign::NoSign || supply > MAXIMUM_SUPPLY.to_owned() @@ -121,7 +139,7 @@ fn process_deploy( ))); } - let limit = Num::from_str(&deploy.mint_limit.map_or(deploy.max_supply, |v| v))?; + let limit = Num::from_str(&deploy.mint_limit.map_or(max_supply, |v| v))?; if limit.sign() == Sign::NoSign || limit > MAXIMUM_SUPPLY.to_owned() @@ -142,12 +160,14 @@ fn process_deploy( tick: tick.clone(), decimal: dec, supply, + burned_supply: 0u128, limit_per_mint: limit, minted: 0u128, deploy_by: to_script_key, - deployed_number: context.chain.blockheight, - latest_mint_number: context.chain.blockheight, - deployed_timestamp: context.chain.blocktime, + is_self_mint, + deployed_number: context.chain_conf.blockheight, + latest_mint_number: context.chain_conf.blockheight, + deployed_timestamp: context.chain_conf.blocktime, }; context .insert_token_info(&tick, &new_info) @@ -158,25 +178,36 @@ fn process_deploy( limit_per_mint: limit, decimal: dec, tick: new_info.tick, + self_mint: is_self_mint, })) } -fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Result { +fn process_mint( + context: &mut Context, + msg: &ExecutionMessage, + mint: Mint, + parent: Option, +) -> Result { // ignore inscribe inscription to coinbase. let to_script_key = msg.to.clone().ok_or(BRC20Error::InscribeToCoinbase)?; let tick = mint.tick.parse::()?; - let token_info = context + let tick_info = context .get_token_info(&tick) .map_err(Error::LedgerError)? .ok_or(BRC20Error::TickNotFound(tick.to_string()))?; - let base = BIGDECIMAL_TEN.checked_powu(u64::from(token_info.decimal))?; + // check if self mint is allowed. + if tick_info.is_self_mint && !parent.is_some_and(|parent| parent == tick_info.inscription_id) { + return Err(Error::BRC20Error(BRC20Error::SelfMintPermissionDenied)); + } + + let base = BIGDECIMAL_TEN.checked_powu(u64::from(tick_info.decimal))?; let mut amt = Num::from_str(&mint.amount)?; - if amt.scale() > i64::from(token_info.decimal) { + if amt.scale() > i64::from(tick_info.decimal) { return Err(Error::BRC20Error(BRC20Error::AmountOverflow( amt.to_string(), ))); @@ -186,17 +217,17 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re if amt.sign() == Sign::NoSign { return Err(Error::BRC20Error(BRC20Error::InvalidZeroAmount)); } - if amt > Into::::into(token_info.limit_per_mint) { + if amt > Into::::into(tick_info.limit_per_mint) { return Err(Error::BRC20Error(BRC20Error::AmountExceedLimit( amt.to_string(), ))); } - let minted = Into::::into(token_info.minted); - let supply = Into::::into(token_info.supply); + let minted = Into::::into(tick_info.minted); + let supply = Into::::into(tick_info.supply); if minted >= supply { return Err(Error::BRC20Error(BRC20Error::TickMinted( - token_info.tick.to_string(), + tick_info.tick.to_string(), ))); } @@ -232,11 +263,11 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re // update token minted. let minted = minted.checked_add(&amt)?.checked_to_u128()?; context - .update_mint_token_info(&tick, minted, context.chain.blockheight) + .update_mint_token_info(&tick, minted, context.chain_conf.blockheight) .map_err(Error::LedgerError)?; Ok(Event::Mint(MintEvent { - tick: token_info.tick, + tick: tick_info.tick, amount: amt.checked_to_u128()?, msg: out_msg, })) @@ -382,6 +413,23 @@ fn process_transfer(context: &mut Context, msg: &ExecutionMessage) -> Result { + let burned_amt = Into::::into(token_info.burned_supply) + .checked_add(&amt)? + .checked_to_u128()?; + context + .update_burned_token_info(&tick, burned_amt) + .map_err(Error::LedgerError)?; + out_msg = Some(format!( + "transfer to op_return, burned supply increased: {}", + amt + )); + } + _ => (), + } + Ok(Event::Transfer(TransferEvent { msg: out_msg, tick: token_info.tick, diff --git a/src/okx/protocol/brc20/msg_resolver.rs b/src/okx/protocol/brc20/msg_resolver.rs index 0c39b14055..f0296cf88a 100644 --- a/src/okx/protocol/brc20/msg_resolver.rs +++ b/src/okx/protocol/brc20/msg_resolver.rs @@ -225,6 +225,7 @@ mod tests { max_supply: "1000".to_string(), mint_limit: Some("10".to_string()), decimals: None, + self_mint: None, }), sat_in_outputs: true, }; diff --git a/src/okx/protocol/brc20/operation/deploy.rs b/src/okx/protocol/brc20/operation/deploy.rs index f9d3f36e7c..607e0bcd96 100644 --- a/src/okx/protocol/brc20/operation/deploy.rs +++ b/src/okx/protocol/brc20/operation/deploy.rs @@ -1,15 +1,44 @@ -use serde::{Deserialize, Serialize}; - +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::str::FromStr; #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] pub struct Deploy { #[serde(rename = "tick")] pub tick: String, #[serde(rename = "max")] pub max_supply: String, - #[serde(rename = "lim")] + #[serde(rename = "lim", skip_serializing_if = "Option::is_none")] pub mint_limit: Option, - #[serde(rename = "dec")] + #[serde(rename = "dec", skip_serializing_if = "Option::is_none")] pub decimals: Option, + #[serde( + default, + rename = "self_mint", + skip_serializing_if = "Option::is_none", + deserialize_with = "de_from_str", + serialize_with = "ser_to_str" + )] + pub self_mint: Option, +} + +fn de_from_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = Option::::deserialize(deserializer)?; + match s { + Some(s) => bool::from_str(&s).map_err(de::Error::custom).map(Some), + None => Ok(None), + } +} + +fn ser_to_str(v: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + match v { + Some(v) => serializer.serialize_str(&v.to_string()), + None => serializer.serialize_none(), + } } #[cfg(test)] @@ -24,6 +53,7 @@ mod tests { max_supply: "12000".to_string(), mint_limit: Some("12".to_string()), decimals: Some("11".to_string()), + self_mint: None, }; assert_eq!( @@ -50,10 +80,135 @@ mod tests { max_supply: "12000".to_string(), mint_limit: Some("12".to_string()), decimals: Some("11".to_string()), + self_mint: None, }) ); } + #[test] + fn test_self_mint_serialize() { + let obj = Deploy { + tick: "abcd".to_string(), + max_supply: "12000".to_string(), + mint_limit: Some("12".to_string()), + decimals: Some("11".to_string()), + self_mint: None, + }; + + assert_eq!( + serde_json::to_string(&obj).unwrap(), + format!( + r##"{{"tick":"{}","max":"{}","lim":"{}","dec":"{}"}}"##, + obj.tick, + obj.max_supply, + obj.mint_limit.as_ref().unwrap(), + obj.decimals.as_ref().unwrap(), + ) + ); + + let obj = Deploy { + self_mint: Some(true), + ..obj + }; + + assert_eq!( + serde_json::to_string(&obj).unwrap(), + format!( + r##"{{"tick":"{}","max":"{}","lim":"{}","dec":"{}","self_mint":"{}"}}"##, + obj.tick, + obj.max_supply, + obj.mint_limit.as_ref().unwrap(), + obj.decimals.as_ref().unwrap(), + obj.self_mint.as_ref().unwrap() + ) + ); + + let obj = Deploy { + self_mint: Some(false), + ..obj + }; + assert_eq!( + serde_json::to_string(&obj).unwrap(), + format!( + r##"{{"tick":"{}","max":"{}","lim":"{}","dec":"{}","self_mint":"{}"}}"##, + obj.tick, + obj.max_supply, + obj.mint_limit.as_ref().unwrap(), + obj.decimals.as_ref().unwrap(), + obj.self_mint.as_ref().unwrap() + ) + ) + } + + #[test] + fn test_self_mint_deserialize() { + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11"}"# + ) + .unwrap(), + RawOperation::Deploy(Deploy { + tick: "abcd".to_string(), + max_supply: "12000".to_string(), + mint_limit: Some("12".to_string()), + decimals: Some("11".to_string()), + self_mint: None, + }) + ); + + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11","self_mint":"true"}"# + ) + .unwrap(), + RawOperation::Deploy(Deploy { + tick: "abcd".to_string(), + max_supply: "12000".to_string(), + mint_limit: Some("12".to_string()), + decimals: Some("11".to_string()), + self_mint: Some(true), + }) + ); + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11","self_mint":"false"}"# + ) + .unwrap(), + RawOperation::Deploy(Deploy { + tick: "abcd".to_string(), + max_supply: "12000".to_string(), + mint_limit: Some("12".to_string()), + decimals: Some("11".to_string()), + self_mint: Some(false), + }) + ); + } + + #[test] + fn test_self_mint_deserialize_with_error_value() { + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11","self_mint":"True"}"# + ) + .unwrap_err(), + JSONError::ParseOperationJsonError("provided string was not `true` or `false`".to_string()) + ); + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11","self_mint":"t"}"# + ) + .unwrap_err(), + JSONError::ParseOperationJsonError("provided string was not `true` or `false`".to_string()) + ); + assert_eq!( + deserialize_brc20( + r#"{"p":"brc-20","op":"deploy","tick":"abcd","max":"12000","lim":"12","dec":"11","self_mint":true}"# + ) + .unwrap_err(), + JSONError::ParseOperationJsonError("invalid type: boolean `true`, expected a string".to_string()) + ); + } + #[test] fn test_loss_require_key() { assert_eq!( @@ -74,6 +229,7 @@ mod tests { max_supply: "100".to_string(), mint_limit: None, decimals: Some("10".to_string()), + self_mint: None, }) ); @@ -86,6 +242,7 @@ mod tests { max_supply: "100".to_string(), mint_limit: Some("10".to_string()), decimals: None, + self_mint: None, }) ); @@ -97,6 +254,7 @@ mod tests { max_supply: "100".to_string(), mint_limit: None, decimals: None, + self_mint: None, }) ); } @@ -111,6 +269,7 @@ mod tests { max_supply: "300".to_string(), mint_limit: Some("20".to_string()), decimals: Some("17".to_string()), + self_mint: None, }) ); } diff --git a/src/okx/protocol/brc20/operation/mod.rs b/src/okx/protocol/brc20/operation/mod.rs index da558eaf71..c4eb706fa4 100644 --- a/src/okx/protocol/brc20/operation/mod.rs +++ b/src/okx/protocol/brc20/operation/mod.rs @@ -12,7 +12,10 @@ pub use self::{deploy::Deploy, mint::Mint, transfer::Transfer}; #[derive(Debug, Clone, PartialEq)] pub enum Operation { Deploy(Deploy), - Mint(Mint), + Mint { + mint: Mint, + parent: Option, + }, InscribeTransfer(Transfer), Transfer(Transfer), } @@ -21,7 +24,7 @@ impl Operation { pub fn op_type(&self) -> OperationType { match self { Operation::Deploy(_) => OperationType::Deploy, - Operation::Mint(_) => OperationType::Mint, + Operation::Mint { .. } => OperationType::Mint, Operation::InscribeTransfer(_) => OperationType::InscribeTransfer, Operation::Transfer(_) => OperationType::Transfer, } @@ -70,7 +73,10 @@ pub(crate) fn deserialize_brc20_operation( match action { Action::New { .. } => match raw_operation { RawOperation::Deploy(deploy) => Ok(Operation::Deploy(deploy)), - RawOperation::Mint(mint) => Ok(Operation::Mint(mint)), + RawOperation::Mint(mint) => Ok(Operation::Mint { + mint, + parent: inscription.parent(), + }), RawOperation::Transfer(transfer) => Ok(Operation::InscribeTransfer(transfer)), }, Action::Transfer => match raw_operation { @@ -115,7 +121,8 @@ mod tests { tick: "ordi".to_string(), max_supply, mint_limit: Some(mint_limit), - decimals: None + decimals: None, + self_mint: None, }) ); } @@ -221,6 +228,7 @@ mod tests { max_supply: "12000".to_string(), mint_limit: Some("12".to_string()), decimals: Some("11".to_string()), + self_mint: None, }), ); let inscription = crate::inscription( @@ -239,10 +247,13 @@ mod tests { }, ) .unwrap(), - Operation::Mint(Mint { - tick: "abcd".to_string(), - amount: "12000".to_string() - }) + Operation::Mint { + mint: Mint { + tick: "abcd".to_string(), + amount: "12000".to_string() + }, + parent: None + } ); let inscription = crate::inscription( content_type, diff --git a/src/okx/protocol/brc20/policies.rs b/src/okx/protocol/brc20/policies.rs new file mode 100644 index 0000000000..8b160c30f8 --- /dev/null +++ b/src/okx/protocol/brc20/policies.rs @@ -0,0 +1,16 @@ +use crate::Chain; + +pub struct HardForks; + +impl HardForks { + /// Proposed block activation height for issuance and burn enhancements. + /// Proposal content: https://l1f.discourse.group/t/brc-20-proposal-for-issuance-and-burn-enhancements-brc20-ip-1/621 + pub fn self_issuance_activation_height(chain: Chain) -> u32 { + match chain { + Chain::Mainnet => 837090, // decided by community + Chain::Testnet => 2413343, // decided by the ourselves + Chain::Regtest => 0, + Chain::Signet => 0, + } + } +} diff --git a/src/okx/protocol/context.rs b/src/okx/protocol/context.rs index 1ae2f19c9e..bdf0d7406b 100644 --- a/src/okx/protocol/context.rs +++ b/src/okx/protocol/context.rs @@ -11,7 +11,8 @@ use crate::{ get_transferable_assets_by_account, get_transferable_assets_by_account_ticker, get_transferable_assets_by_outpoint, get_transferable_assets_by_satpoint, insert_token_info, insert_transferable_asset, remove_transferable_asset, - save_transaction_receipts, update_mint_token_info, update_token_balance, + save_transaction_receipts, update_burned_token_info, update_mint_token_info, + update_token_balance, }, Balance, Brc20Reader, Brc20ReaderWriter, Receipt, Tick, TokenInfo, TransferableLog, }, @@ -28,17 +29,17 @@ use crate::{ ScriptKey, }, lru::SimpleLru, - protocol::BlockContext, + protocol::ChainContext, }, - SatPoint, + Chain, SatPoint, }; use anyhow::anyhow; -use bitcoin::{Network, OutPoint, TxOut, Txid}; +use bitcoin::{OutPoint, TxOut, Txid}; use redb::{MultimapTable, Table}; #[allow(non_snake_case)] pub struct Context<'a, 'db, 'txn> { - pub(crate) chain: BlockContext, + pub(crate) chain_conf: ChainContext, pub(crate) tx_out_cache: &'a mut SimpleLru, pub(crate) hit: u64, pub(crate) miss: u64, @@ -84,15 +85,15 @@ impl<'a, 'db, 'txn> OrdReader for Context<'a, 'db, 'txn> { fn get_script_key_on_satpoint( &mut self, satpoint: &SatPoint, - network: Network, + chain: Chain, ) -> crate::Result { if let Some(tx_out) = self.tx_out_cache.get(&satpoint.outpoint) { self.hit += 1; - Ok(ScriptKey::from_script(&tx_out.script_pubkey, network)) + Ok(ScriptKey::from_script(&tx_out.script_pubkey, chain)) } else if let Some(tx_out) = get_txout_by_outpoint(self.OUTPOINT_TO_ENTRY, &satpoint.outpoint)? { self.miss += 1; - Ok(ScriptKey::from_script(&tx_out.script_pubkey, network)) + Ok(ScriptKey::from_script(&tx_out.script_pubkey, chain)) } else { Err(anyhow!( "failed to get tx out! error: outpoint {} not found", @@ -248,6 +249,14 @@ impl<'a, 'db, 'txn> Brc20ReaderWriter for Context<'a, 'db, 'txn> { update_mint_token_info(self.BRC20_TOKEN, tick, minted_amt, minted_block_number) } + fn update_burned_token_info( + &mut self, + tick: &Tick, + burned_amt: u128, + ) -> crate::Result<(), Self::Error> { + update_burned_token_info(self.BRC20_TOKEN, tick, burned_amt) + } + fn save_transaction_receipts( &mut self, txid: &Txid, diff --git a/src/okx/protocol/execute_manager.rs b/src/okx/protocol/execute_manager.rs index a4c7aad11f..a1944e5a20 100644 --- a/src/okx/protocol/execute_manager.rs +++ b/src/okx/protocol/execute_manager.rs @@ -26,8 +26,11 @@ impl CallManager { for msg in msgs { match msg { Message::BRC20(brc_msg) => { - let msg = - brc20_proto::ExecutionMessage::from_message(context, brc_msg, context.chain.network)?; + let msg = brc20_proto::ExecutionMessage::from_message( + context, + brc_msg, + context.chain_conf.chain, + )?; let receipt = brc20_proto::execute(context, &msg)?; receipts.push(receipt); } diff --git a/src/okx/protocol/mod.rs b/src/okx/protocol/mod.rs index c7dc147bdc..947ef633a9 100644 --- a/src/okx/protocol/mod.rs +++ b/src/okx/protocol/mod.rs @@ -10,13 +10,12 @@ pub use self::protocol_manager::ProtocolManager; use { self::{execute_manager::CallManager, message::Message, resolve_manager::MsgResolveManager}, - crate::Options, - bitcoin::Network, + crate::{Chain, Options}, }; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BlockContext { - pub network: Network, +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ChainContext { + pub chain: Chain, pub blockheight: u32, pub blocktime: u32, } diff --git a/src/okx/protocol/ord/bitmap.rs b/src/okx/protocol/ord/bitmap.rs index 73c6d6c8bf..cdb52e57b5 100644 --- a/src/okx/protocol/ord/bitmap.rs +++ b/src/okx/protocol/ord/bitmap.rs @@ -64,7 +64,7 @@ fn index_district( ) -> Result> { if let Some(content) = inscription.body() { if let Ok(district) = District::parse(content) { - if district.number > context.chain.blockheight { + if district.number > context.chain_conf.blockheight { return Ok(None); } let collection_key = district.to_collection_key(); diff --git a/src/okx/protocol/protocol_manager.rs b/src/okx/protocol/protocol_manager.rs index b1541088bd..e3a4d0ab66 100644 --- a/src/okx/protocol/protocol_manager.rs +++ b/src/okx/protocol/protocol_manager.rs @@ -54,7 +54,7 @@ impl ProtocolManager { if let Some(tx_operations) = operations.get(txid) { // save all transaction operations to ord database. if self.config.enable_ord_receipts - && context.chain.blockheight >= self.config.first_inscription_height + && context.chain_conf.blockheight >= self.config.first_inscription_height { let start = Instant::now(); context.save_transaction_operations(txid, tx_operations)?; @@ -85,7 +85,7 @@ impl ProtocolManager { log::info!( "Protocol Manager indexed block {} with ord inscriptions {}, messages {}, bitmap {} in {} ms, {}/{}/{}/{}", - context.chain.blockheight, + context.chain_conf.blockheight, inscriptions_size, messages_size, bitmap_count, diff --git a/src/okx/protocol/resolve_manager.rs b/src/okx/protocol/resolve_manager.rs index 5705f34ea7..3c1dd936d0 100644 --- a/src/okx/protocol/resolve_manager.rs +++ b/src/okx/protocol/resolve_manager.rs @@ -57,7 +57,7 @@ impl MsgResolveManager { if self .config .first_brc20_height - .map(|height| context.chain.blockheight >= height) + .map(|height| context.chain_conf.blockheight >= height) .unwrap_or(false) { let satpoint_to_transfer_assets: HashMap = diff --git a/src/subcommand/server/brc20/balance.rs b/src/subcommand/server/brc20/balance.rs index 3c31ad17f3..de20d292af 100644 --- a/src/subcommand/server/brc20/balance.rs +++ b/src/subcommand/server/brc20/balance.rs @@ -41,10 +41,10 @@ pub(crate) async fn brc20_balance( log::debug!("rpc: get brc20_balance: {} {}", tick, address); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); let ticker = Tick::from_str(&tick).map_err(|_| BRC20ApiError::InvalidTicker(tick.clone()))?; - let script_key = utils::parse_and_validate_script_key_network(&address, network) + let script_key = utils::parse_and_validate_script_key_with_chain(&address, chain) .map_err(ApiError::bad_request)?; let balance = Index::get_brc20_balance_by_tick_and_address(ticker, script_key, &rtx)? @@ -93,9 +93,9 @@ pub(crate) async fn brc20_all_balance( log::debug!("rpc: get brc20_all_balance: {}", account); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); - let script_key = utils::parse_and_validate_script_key_network(&account, network) + let script_key = utils::parse_and_validate_script_key_with_chain(&account, chain) .map_err(ApiError::bad_request)?; let all_balance = rtx.brc20_get_all_balance_by_address(script_key)?; diff --git a/src/subcommand/server/brc20/receipt.rs b/src/subcommand/server/brc20/receipt.rs index 59134964d3..1b0a0528fc 100644 --- a/src/subcommand/server/brc20/receipt.rs +++ b/src/subcommand/server/brc20/receipt.rs @@ -109,6 +109,8 @@ pub struct ApiDeployEvent { pub limit_per_mint: String, /// The decimal of the deployed ticker. pub decimal: u8, + /// Whether the ticker is self minted. + pub self_mint: bool, /// The message sender which is an address or script pubkey hash. pub from: ScriptPubkey, /// The message receiver which is an address or script pubkey hash. @@ -130,6 +132,7 @@ impl ApiDeployEvent { supply: deploy_event.supply.to_string(), limit_per_mint: deploy_event.limit_per_mint.to_string(), decimal: deploy_event.decimal, + self_mint: deploy_event.self_mint, from: event.from.clone().into(), to: event.to.clone().into(), valid: true, diff --git a/src/subcommand/server/brc20/ticker.rs b/src/subcommand/server/brc20/ticker.rs index d2c221d6d3..6ab93a1966 100644 --- a/src/subcommand/server/brc20/ticker.rs +++ b/src/subcommand/server/brc20/ticker.rs @@ -23,6 +23,11 @@ pub struct ApiTickInfo { /// We represent u64 values as a string to ensure compatibility with languages such as JavaScript that do not parse u64s in JSON natively. #[schema(format = "uint64")] pub supply: String, + /// The amount of the ticker that has been burned. + #[schema(format = "uint64")] + pub burned_supply: String, + /// Whether the ticker is self minted. + pub self_mint: bool, /// The maximum amount of each mining. #[schema(format = "uint64")] pub limit_per_mint: String, @@ -59,9 +64,11 @@ impl From for ApiTickInfo { inscription_id: tick_info.inscription_id.to_string(), inscription_number: tick_info.inscription_number, supply: tick_info.supply.to_string(), + burned_supply: tick_info.burned_supply.to_string(), limit_per_mint: tick_info.limit_per_mint.to_string(), minted: tick_info.minted.to_string(), decimal: tick_info.decimal, + self_mint: tick_info.is_self_mint, deploy_by: tick_info.deploy_by.clone().into(), txid: tick_info.inscription_id.txid.to_string(), deploy_height: tick_info.deployed_number, diff --git a/src/subcommand/server/brc20/transferable.rs b/src/subcommand/server/brc20/transferable.rs index 5740da83e4..77be213dcb 100644 --- a/src/subcommand/server/brc20/transferable.rs +++ b/src/subcommand/server/brc20/transferable.rs @@ -43,10 +43,10 @@ pub(crate) async fn brc20_transferable( log::debug!("rpc: get brc20_transferable: {tick} {address}"); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); let ticker = Tick::from_str(&tick).map_err(|_| BRC20ApiError::InvalidTicker(tick.clone()))?; - let script_key = utils::parse_and_validate_script_key_network(&address, network) + let script_key = utils::parse_and_validate_script_key_with_chain(&address, chain) .map_err(ApiError::bad_request)?; let brc20_transferable_assets = @@ -108,9 +108,9 @@ pub(crate) async fn brc20_all_transferable( log::debug!("rpc: get brc20_all_transferable: {account}"); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); - let script_key = utils::parse_and_validate_script_key_network(&account, network) + let script_key = utils::parse_and_validate_script_key_with_chain(&account, chain) .map_err(ApiError::bad_request)?; let brc20_transferable_assets = rtx.brc20_get_all_transferable_by_address(script_key)?; diff --git a/src/subcommand/server/info.rs b/src/subcommand/server/info.rs index 7fc9a3b9a1..b17eeeddce 100644 --- a/src/subcommand/server/info.rs +++ b/src/subcommand/server/info.rs @@ -85,7 +85,7 @@ pub(crate) async fn node_info( commit_hash: Some(build::SHORT_COMMIT.into()), build_time: Some(build::BUILD_TIME.into()), chain_info: ChainInfo { - network: Some(index.get_chain_network().to_string()), + network: Some(index.get_chain().to_string()), ord_block_height: latest_height.0, ord_block_hash: latest_blockhash.to_string(), chain_block_height, diff --git a/src/subcommand/server/ord/inscription.rs b/src/subcommand/server/ord/inscription.rs index 3d31c99414..376b4c29cd 100644 --- a/src/subcommand/server/ord/inscription.rs +++ b/src/subcommand/server/ord/inscription.rs @@ -80,13 +80,13 @@ pub(crate) async fn ord_inscription_id( log::debug!("rpc: get ord_inscription_id: {id}"); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); let client = index.bitcoin_rpc_client()?; let index_transactions = index.has_transactions_index(); let id = InscriptionId::from_str(&id).map_err(ApiError::bad_request)?; - ord_get_inscription_by_id(id, &rtx, client, network, index_transactions) + ord_get_inscription_by_id(id, &rtx, client, chain, index_transactions) } // /ord/number/:number/inscription @@ -111,34 +111,29 @@ pub(crate) async fn ord_inscription_number( log::debug!("rpc: get ord_inscription_number: {number}"); let rtx = index.begin_read()?; - let network = index.get_chain_network(); + let chain = index.get_chain(); let client = index.bitcoin_rpc_client()?; let index_transactions = index.has_transactions_index(); let inscription_id = Index::get_inscription_id_by_inscription_number_with_rtx(number, &rtx)? .ok_or(OrdApiError::UnknownInscriptionNumber(number))?; - ord_get_inscription_by_id(inscription_id, &rtx, client, network, index_transactions) + ord_get_inscription_by_id(inscription_id, &rtx, client, chain, index_transactions) } fn ord_get_inscription_by_id( inscription_id: InscriptionId, rtx: &Rtx, client: Client, - network: Network, + chain: Chain, index_transactions: bool, ) -> ApiResult { let inscription_entry = Index::get_inscription_entry_with_rtx(inscription_id, rtx)? .ok_or(OrdApiError::UnknownInscriptionId(inscription_id))?; - let tx = Index::get_transaction_with_rtx( - inscription_id.txid, - rtx, - &client, - network, - index_transactions, - )? - .ok_or(OrdApiError::TransactionNotFound(inscription_id.txid))?; + let tx = + Index::get_transaction_with_rtx(inscription_id.txid, rtx, &client, chain, index_transactions)? + .ok_or(OrdApiError::TransactionNotFound(inscription_id.txid))?; let inscription = ParsedEnvelope::from_transaction(&tx) .get(usize::try_from(inscription_id.index).unwrap()) @@ -168,7 +163,7 @@ fn ord_get_inscription_by_id( location_outpoint.txid, rtx, &client, - network, + chain, index_transactions, )? .ok_or(OrdApiError::TransactionNotFound(location_outpoint.txid))? @@ -195,7 +190,7 @@ fn ord_get_inscription_by_id( parent: inscription.parent(), pointer: inscription.pointer(), delegate: inscription.delegate(), - owner: output.map(|vout| ScriptKey::from_script(&vout.script_pubkey, network).into()), + owner: output.map(|vout| ScriptKey::from_script(&vout.script_pubkey, chain).into()), genesis_height: inscription_entry.height, genesis_timestamp: inscription_entry.timestamp, location: sat_point.to_string(), @@ -287,7 +282,7 @@ mod tests { .unwrap() .assume_checked() .script_pubkey(), - Network::Bitcoin, + Chain::Mainnet, ) .into(), ), diff --git a/src/subcommand/server/ord/outpoint.rs b/src/subcommand/server/ord/outpoint.rs index 8c0daef32f..8d4f438731 100644 --- a/src/subcommand/server/ord/outpoint.rs +++ b/src/subcommand/server/ord/outpoint.rs @@ -75,6 +75,7 @@ pub(crate) async fn ord_outpoint( })?; let inscriptions_with_satpoints = rtx.inscriptions_on_output_with_satpoints(outpoint)?; + let chain = index.get_chain(); // If there are no inscriptions on the output, return None and parsed block states. if inscriptions_with_satpoints.is_empty() { @@ -102,7 +103,7 @@ pub(crate) async fn ord_outpoint( &rtx, &index.bitcoin_rpc_client()?, outpoint, - index.get_chain_network(), + chain, index.has_transactions_index(), )? .ok_or(OrdApiError::TransactionNotFound(outpoint.txid))?; @@ -111,7 +112,7 @@ pub(crate) async fn ord_outpoint( result: Some(ApiOutpointInscriptions { txid: outpoint.txid.to_string(), script_pub_key: vout.script_pubkey.to_asm_string(), - owner: ScriptKey::from_script(&vout.script_pubkey, index.get_chain_network()).into(), + owner: ScriptKey::from_script(&vout.script_pubkey, chain).into(), value: vout.value, inscription_digest: inscription_digests, }), diff --git a/src/subcommand/server/ord/transaction.rs b/src/subcommand/server/ord/transaction.rs index 0562e77e74..dcab8008fb 100644 --- a/src/subcommand/server/ord/transaction.rs +++ b/src/subcommand/server/ord/transaction.rs @@ -58,14 +58,14 @@ impl ApiTxInscription { operation: InscriptionOp, rtx: &Rtx, client: &Client, - network: Network, + chain: Chain, index_transactions: bool, ) -> Result { let prevout = Index::fetch_vout( rtx, client, operation.old_satpoint.outpoint, - network, + chain, index_transactions, )? .ok_or(OrdApiError::Internal(format!( @@ -78,15 +78,15 @@ impl ApiTxInscription { rtx, client, new_satpoint.outpoint, - network, + chain, index_transactions, )?, _ => None, }; Ok(ApiTxInscription { - from: ScriptKey::from_script(&prevout.script_pubkey, network).into(), - to: output.map(|v| ScriptKey::from_script(&v.script_pubkey, network).into()), + from: ScriptKey::from_script(&prevout.script_pubkey, chain).into(), + to: output.map(|v| ScriptKey::from_script(&v.script_pubkey, chain).into()), action: operation.action.into(), inscription_number: operation.inscription_number, inscription_id: operation.inscription_id.to_string(), @@ -148,7 +148,7 @@ pub(crate) async fn ord_txid_inscriptions( operation, &rtx, &client, - index.get_chain_network(), + index.get_chain(), index_transactions, )?; api_tx_inscriptions.push(tx_inscription); @@ -197,7 +197,7 @@ pub(crate) async fn ord_block_inscriptions( operation, &rtx, &client, - index.get_chain_network(), + index.get_chain(), index_transactions, )?; api_tx_operations.push(tx_inscription); @@ -227,7 +227,7 @@ mod tests { .unwrap() .assume_checked() .script_pubkey(), - Network::Bitcoin, + Chain::Mainnet, ) .into(), to: Some( @@ -236,7 +236,7 @@ mod tests { .unwrap() .assume_checked() .script_pubkey(), - Network::Bitcoin, + Chain::Mainnet, ) .into(), ), diff --git a/src/subcommand/server/types.rs b/src/subcommand/server/types.rs index 1a8d49dea1..49971f120b 100644 --- a/src/subcommand/server/types.rs +++ b/src/subcommand/server/types.rs @@ -20,7 +20,9 @@ impl From for ScriptPubkey { fn from(script_key: ScriptKey) -> Self { match script_key { ScriptKey::Address(address) => ScriptPubkey::Address(address.assume_checked().to_string()), - ScriptKey::ScriptHash(hash) => ScriptPubkey::NonStandard(hash.to_string()), + ScriptKey::ScriptHash { script_hash, .. } => { + ScriptPubkey::NonStandard(script_hash.to_string()) + } } } } @@ -34,7 +36,7 @@ mod tests { .unwrap() .assume_checked() .script_pubkey(), - Network::Bitcoin, + Chain::Mainnet, ) .into(); assert_eq!( @@ -49,7 +51,7 @@ mod tests { .unwrap() .as_slice(), ), - Network::Bitcoin, + Chain::Mainnet, ) .into(); diff --git a/src/subcommand/server/utils.rs b/src/subcommand/server/utils.rs index 6a441f0b54..e358334e27 100644 --- a/src/subcommand/server/utils.rs +++ b/src/subcommand/server/utils.rs @@ -2,17 +2,20 @@ use self::okx::datastore::ScriptKey; use super::*; use bitcoin::ScriptHash; -pub(crate) fn parse_and_validate_script_key_network( +pub(crate) fn parse_and_validate_script_key_with_chain( key: &str, - network: Network, + chain: Chain, ) -> Result { if let Ok(address) = Address::from_str(key) { - match address.clone().require_network(network) { + match address.clone().require_network(chain.network()) { Ok(_) => Ok(ScriptKey::Address(address)), - Err(_) => Err(anyhow!("invalid network: {} for address: {}", network, key)), + Err(_) => Err(anyhow!("invalid chain: {} for address: {}", chain, key)), } } else if let Ok(script_hash) = ScriptHash::from_str(key) { - Ok(ScriptKey::ScriptHash(script_hash)) + Ok(ScriptKey::ScriptHash { + script_hash, + is_op_return: false, + }) } else { Err(anyhow!("invalid script key: {}", key)) }