From e5a0d2807afcd9d801dac605a7bdee715e9dbaa3 Mon Sep 17 00:00:00 2001 From: nandhh Date: Fri, 28 Mar 2025 11:47:26 +0800 Subject: [PATCH 01/12] feat: outter version single step transfer # Conflicts: # src/subcommand/server/okx/brc20/balance.rs --- src/index/bundle_message.rs | 59 +++++++++++++++++- src/index/event.rs | 1 + src/index/updater/inscription_updater.rs | 18 ++++++ src/okx/brc20/entry.rs | 2 + src/okx/brc20/error.rs | 3 + src/okx/brc20/executor.rs | 10 +-- src/okx/brc20/executor/inscribe_transfer.rs | 67 +++++++++++++++++---- src/subcommand/server/okx/brc20/balance.rs | 3 + 8 files changed, 145 insertions(+), 18 deletions(-) diff --git a/src/index/bundle_message.rs b/src/index/bundle_message.rs index 6ca37acd37..05434ffa3b 100644 --- a/src/index/bundle_message.rs +++ b/src/index/bundle_message.rs @@ -8,6 +8,13 @@ use crate::{ UtxoAddress, }, }; +use { + bitcoin::{ + secp256k1::{XOnlyPublicKey}, + script::{ScriptBuf}, + key::{TweakedPublicKey}, + }, +}; #[derive(Debug, Clone)] pub enum SubType { @@ -21,6 +28,7 @@ pub enum InscriptionAction { Created { charms: u16, sub_type: Option, + signer: Option, }, Transferred, } @@ -77,7 +85,16 @@ impl BundleMessage { sender: event.sender, receiver: event.receiver, inscription_action: match event.action { - Action::Created { charms, .. } => InscriptionAction::Created { charms, sub_type }, + Action::Created { charms, tapscript_pk, .. } => { + let address_type = tapscript_pk[34]; + let signer = if address_type > 0 { + let script = get_pk_script_by_pubkey_and_type(&tapscript_pk[1..33], address_type); + Some(UtxoAddress::from_script(script.as_script(), &index.settings.chain())) + } else { + None + }; + InscriptionAction::Created { charms, sub_type, signer, } + }, Action::Transferred => InscriptionAction::Transferred, }, })) @@ -119,3 +136,43 @@ fn extract_sub_type( } Ok(None) } + +/// Get a script pubkey based on the provided pubkey and address type +pub fn get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes: &[u8], address_type: u8) -> ScriptBuf { + const BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT: u8 = 0x51; + const BRC20_PUBKEY_ADDRESS_P2PKH_EVEN: u8 = 0x52; + const BRC20_PUBKEY_ADDRESS_P2PKH_ODD: u8 = 0x53; + const BRC20_PUBKEY_ADDRESS_P2WPKH: u8 = 0x54; + const BRC20_PUBKEY_ADDRESS_P2TR_KEY: u8 = 0x55; + const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH: u8 = 0x56; + + let x_only_pubkey = XOnlyPublicKey::from_slice(x_only_pubkey_bytes).unwrap(); + match address_type { + BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT => { + let secp = bitcoin::secp256k1::Secp256k1::verification_only(); + ScriptBuf::new_p2tr(&secp, x_only_pubkey, None) + }, + BRC20_PUBKEY_ADDRESS_P2PKH_EVEN | BRC20_PUBKEY_ADDRESS_P2PKH_ODD => { + let parity = if address_type == BRC20_PUBKEY_ADDRESS_P2PKH_EVEN { + bitcoin::secp256k1::Parity::Even + } else { + bitcoin::secp256k1::Parity::Odd + }; + let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(parity)); + ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()) + }, + BRC20_PUBKEY_ADDRESS_P2WPKH | BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH => { + let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(bitcoin::secp256k1::Parity::Even)); + let wpkh_script = ScriptBuf::new_p2wpkh(&pubkey.wpubkey_hash().unwrap()); + if address_type == BRC20_PUBKEY_ADDRESS_P2WPKH { + wpkh_script + } else { + ScriptBuf::new_p2sh(&wpkh_script.script_hash()) + } + }, + BRC20_PUBKEY_ADDRESS_P2TR_KEY => { + ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey)) + }, + _ => ScriptBuf::new(), + } +} diff --git a/src/index/event.rs b/src/index/event.rs index e1da37f469..dc888a6ab9 100644 --- a/src/index/event.rs +++ b/src/index/event.rs @@ -51,6 +51,7 @@ pub(crate) enum Action { parents: Vec, pre_jubilant_curse_reason: Option, charms: u16, + tapscript_pk: [u8; 35], }, Transferred, } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 7808af6fd4..b5fd84328a 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -1,4 +1,5 @@ use super::*; +use bitcoin::blockdata::opcodes; use crate::index::bundle_message::BundleMessage; use crate::index::event::{Action, OkxInscriptionEvent}; use crate::okx::UtxoAddress; @@ -38,6 +39,7 @@ enum Origin { vindicated: bool, inscription: Inscription, pre_jubilant_curse_reason: Option, + tapscript_pk: [u8; 35], }, Old { sequence_number: u32, @@ -155,6 +157,19 @@ impl InscriptionUpdater<'_, '_> { break; } + let mut tapscript_pk = [0u8; 35]; + if let Some(tapscript) = txin.witness.tapscript() { + if tapscript.len() >= 35 { + let script_bytes = tapscript.as_bytes(); + if script_bytes[0] == opcodes::all::OP_PUSHBYTES_32.to_u8() + && script_bytes[33] == opcodes::all::OP_CHECKSIGVERIFY.to_u8() + && script_bytes[34] >= opcodes::all::OP_PUSHNUM_1.to_u8() + && script_bytes[34] <= opcodes::all::OP_PUSHNUM_6.to_u8() { + tapscript_pk.copy_from_slice(&script_bytes[..35]); + } + } + } + let inscription = envelopes.next().unwrap(); let inscription_id = InscriptionId { @@ -233,6 +248,7 @@ impl InscriptionUpdater<'_, '_> { vindicated: curse.is_some() && jubilant, inscription: inscription.payload, pre_jubilant_curse_reason: curse, + tapscript_pk: tapscript_pk, }, }); @@ -620,12 +636,14 @@ impl InscriptionUpdater<'_, '_> { inscription, parents, pre_jubilant_curse_reason, + tapscript_pk, .. } => Action::Created { inscription, parents, pre_jubilant_curse_reason, charms: charms.unwrap(), + tapscript_pk, }, Origin::Old { .. } => Action::Transferred, }, diff --git a/src/okx/brc20/entry.rs b/src/okx/brc20/entry.rs index 82327545ca..553075e207 100644 --- a/src/okx/brc20/entry.rs +++ b/src/okx/brc20/entry.rs @@ -8,6 +8,7 @@ pub struct BRC20Balance { pub ticker: BRC20Ticker, pub total: u128, pub available: u128, + pub single_step_transfer: bool, } impl BRC20Balance { @@ -16,6 +17,7 @@ impl BRC20Balance { ticker: ticker.clone(), total: 0, available: 0, + single_step_transfer: false, } } } diff --git a/src/okx/brc20/error.rs b/src/okx/brc20/error.rs index f60ba64110..c339b69458 100644 --- a/src/okx/brc20/error.rs +++ b/src/okx/brc20/error.rs @@ -38,4 +38,7 @@ pub enum BRC20Error { #[error("Numeric error occurred: {0}")] NumericError(#[from] fixed_point::NumParseError), + + #[error("Legacy-transfer operation denied: insufficient permissions")] + LegacyTransferPermissionDenied, } diff --git a/src/okx/brc20/executor.rs b/src/okx/brc20/executor.rs index 0c740e7b5d..2dfb61b112 100644 --- a/src/okx/brc20/executor.rs +++ b/src/okx/brc20/executor.rs @@ -21,6 +21,7 @@ pub(crate) struct BRC20ExecutionMessage { sender: UtxoAddress, receiver: Option, // no address, if unbound operation: BRC20Operation, + signer: Option, } impl BRC20ExecutionMessage { @@ -28,7 +29,7 @@ impl BRC20ExecutionMessage { value: &BundleMessage, context: &mut TableContext, ) -> Result> { - let build_message = |operation| { + let build_message = |operation, signer| { Ok(Some(Self { txid: value.txid, inscription_id: value.inscription_id, @@ -38,14 +39,15 @@ impl BRC20ExecutionMessage { new_satpoint: value.new_satpoint, sender: value.sender.clone(), receiver: value.receiver.clone(), + signer: signer, operation, })) }; match &value.inscription_action { - InscriptionAction::Created { sub_type, .. } => { + InscriptionAction::Created { sub_type, signer, .. } => { if let Some(SubType::BRC20(brc20_operation)) = sub_type { - build_message(brc20_operation.clone()) + build_message(brc20_operation.clone(), signer.clone()) } else { Ok(None) } @@ -53,7 +55,7 @@ impl BRC20ExecutionMessage { InscriptionAction::Transferred => match Option::::from(value) { Some(transferred_inscription) => { match transferred_inscription.extract_and_validate_transfer(context) { - Ok(Some(brc20_operation)) => build_message(brc20_operation), + Ok(Some(brc20_operation)) => build_message(brc20_operation, None), Ok(None) => Ok(None), Err(err) => Err(err), } diff --git a/src/okx/brc20/executor/inscribe_transfer.rs b/src/okx/brc20/executor/inscribe_transfer.rs index db313bfca2..9ee0b8d638 100644 --- a/src/okx/brc20/executor/inscribe_transfer.rs +++ b/src/okx/brc20/executor/inscribe_transfer.rs @@ -28,14 +28,39 @@ impl BRC20ExecutionMessage { ))); } - let address = self.receiver.clone().unwrap(); - - let mut balance = context - .load_brc20_balance(&address, &ticker)? + let mut sender_or_legacy = self.sender.clone(); + let receiver = self.receiver.clone().unwrap(); + let mut sender = receiver.clone(); + let mut sender_balance = context + .load_brc20_balance(&sender, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); - let available = FixedPoint::new_unchecked(balance.available, ticker_info.decimals); - balance.available = available + if sender_balance.single_step_transfer && self.signer == None { + return Err(ExecutionError::ExecutionFailed( + BRC20Error::LegacyTransferPermissionDenied, + )); + } + + let mut receiver_balance: Option = None; + if let Some(signer) = self.signer.clone() { + sender_or_legacy = signer.clone(); + if signer != sender { + receiver_balance = Some(sender_balance); + + sender = signer; + sender_balance = context + .load_brc20_balance(&sender, &ticker)? + .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); + } + + if !sender_balance.single_step_transfer { + sender_balance.single_step_transfer = true; + context.update_brc20_balance(&sender, &ticker, sender_balance.clone())?; + } + } + + let available = FixedPoint::new_unchecked(sender_balance.available, ticker_info.decimals); + sender_balance.available = available .checked_sub(amt) .ok_or(ExecutionError::ExecutionFailed( BRC20Error::InsufficientBalance(available, amt), @@ -43,19 +68,35 @@ impl BRC20ExecutionMessage { .to_u128_and_scale() .0; - context.update_brc20_balance(&address, &ticker, balance)?; + let amount = amt.to_u128_and_scale().0; + + if let Some(mut receiver_balance) = receiver_balance { + sender_balance.total = sender_balance + .total + .checked_sub(amount) + .expect("Subtraction overflow"); + + receiver_balance.total = receiver_balance + .total + .checked_add(amount) + .expect("Addition overflow"); + + context.update_brc20_balance(&receiver, &ticker, receiver_balance)?; + } + + context.update_brc20_balance(&sender, &ticker, sender_balance)?; let transferring_asset = BRC20TransferAsset { ticker: ticker.clone(), - amount: amt.to_u128_and_scale().0, - owner: address.clone(), + amount: amount, + owner: receiver.clone(), sequence_number: self.sequence_number, inscription_number: self.inscription_number, inscription_id: self.inscription_id, }; context.insert_brc20_transferring_asset( - &address, + &receiver, &ticker, self.new_satpoint, transferring_asset, @@ -67,12 +108,12 @@ impl BRC20ExecutionMessage { inscription_number: self.inscription_number, old_satpoint: self.old_satpoint, new_satpoint: self.new_satpoint, - sender: self.sender.clone(), - receiver: address, + sender: sender_or_legacy, + receiver: receiver, op_type: BRC20OpType::InscribeTransfer, result: Ok(BRC20Event::InscribeTransfer(InscribeTransferEvent { ticker, - amount: amt.to_u128_and_scale().0, + amount: amount, })), }) } diff --git a/src/subcommand/server/okx/brc20/balance.rs b/src/subcommand/server/okx/brc20/balance.rs index 6fab520d32..063c7f4b84 100644 --- a/src/subcommand/server/okx/brc20/balance.rs +++ b/src/subcommand/server/okx/brc20/balance.rs @@ -7,6 +7,7 @@ pub struct ApiBalance { pub available_balance: String, pub transferable_balance: String, pub overall_balance: String, + pub single_step_transfer: bool, } /// Get the ticker balance of the address. @@ -45,6 +46,7 @@ pub(crate) async fn brc20_balance( available_balance: balance.available.to_string(), transferable_balance: (balance.total - balance.available).to_string(), overall_balance: balance.total.to_string(), + single_step_transfer: balance.single_step_transfer, }))) }) } @@ -81,6 +83,7 @@ pub(crate) async fn brc20_all_balance( available_balance: balance.available.to_string(), transferable_balance: (balance.total - balance.available).to_string(), overall_balance: balance.total.to_string(), + single_step_transfer: balance.single_step_transfer, }) .collect(), }))) From 454b039cb206ff57bf07907b33b25d1fc25cf97f Mon Sep 17 00:00:00 2001 From: nandhh Date: Fri, 28 Mar 2025 11:48:20 +0800 Subject: [PATCH 02/12] feat: inner version single-step transfer --- src/index/bundle_message.rs | 61 +------------ src/okx/brc20.rs | 23 ++++- src/okx/brc20/event.rs | 2 +- src/okx/brc20/executor.rs | 12 ++- src/okx/brc20/executor/inscribe_transfer.rs | 6 +- src/okx/brc20/utils.rs | 96 +++++++++++++++++++++ 6 files changed, 128 insertions(+), 72 deletions(-) create mode 100644 src/okx/brc20/utils.rs diff --git a/src/index/bundle_message.rs b/src/index/bundle_message.rs index 05434ffa3b..1d7d8fa5be 100644 --- a/src/index/bundle_message.rs +++ b/src/index/bundle_message.rs @@ -8,13 +8,6 @@ use crate::{ UtxoAddress, }, }; -use { - bitcoin::{ - secp256k1::{XOnlyPublicKey}, - script::{ScriptBuf}, - key::{TweakedPublicKey}, - }, -}; #[derive(Debug, Clone)] pub enum SubType { @@ -28,7 +21,6 @@ pub enum InscriptionAction { Created { charms: u16, sub_type: Option, - signer: Option, }, Transferred, } @@ -58,7 +50,7 @@ impl BundleMessage { if let Some(SubType::BRC20(operation)) = sub_type { return !matches!( operation, - BRC20Operation::Mint { .. } | BRC20Operation::InscribeTransfer(_) + BRC20Operation::Mint { .. } | BRC20Operation::InscribeTransfer{ .. } ); } } @@ -85,16 +77,7 @@ impl BundleMessage { sender: event.sender, receiver: event.receiver, inscription_action: match event.action { - Action::Created { charms, tapscript_pk, .. } => { - let address_type = tapscript_pk[34]; - let signer = if address_type > 0 { - let script = get_pk_script_by_pubkey_and_type(&tapscript_pk[1..33], address_type); - Some(UtxoAddress::from_script(script.as_script(), &index.settings.chain())) - } else { - None - }; - InscriptionAction::Created { charms, sub_type, signer, } - }, + Action::Created { charms, .. } => InscriptionAction::Created { charms, sub_type }, Action::Transferred => InscriptionAction::Transferred, }, })) @@ -136,43 +119,3 @@ fn extract_sub_type( } Ok(None) } - -/// Get a script pubkey based on the provided pubkey and address type -pub fn get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes: &[u8], address_type: u8) -> ScriptBuf { - const BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT: u8 = 0x51; - const BRC20_PUBKEY_ADDRESS_P2PKH_EVEN: u8 = 0x52; - const BRC20_PUBKEY_ADDRESS_P2PKH_ODD: u8 = 0x53; - const BRC20_PUBKEY_ADDRESS_P2WPKH: u8 = 0x54; - const BRC20_PUBKEY_ADDRESS_P2TR_KEY: u8 = 0x55; - const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH: u8 = 0x56; - - let x_only_pubkey = XOnlyPublicKey::from_slice(x_only_pubkey_bytes).unwrap(); - match address_type { - BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT => { - let secp = bitcoin::secp256k1::Secp256k1::verification_only(); - ScriptBuf::new_p2tr(&secp, x_only_pubkey, None) - }, - BRC20_PUBKEY_ADDRESS_P2PKH_EVEN | BRC20_PUBKEY_ADDRESS_P2PKH_ODD => { - let parity = if address_type == BRC20_PUBKEY_ADDRESS_P2PKH_EVEN { - bitcoin::secp256k1::Parity::Even - } else { - bitcoin::secp256k1::Parity::Odd - }; - let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(parity)); - ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()) - }, - BRC20_PUBKEY_ADDRESS_P2WPKH | BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH => { - let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(bitcoin::secp256k1::Parity::Even)); - let wpkh_script = ScriptBuf::new_p2wpkh(&pubkey.wpubkey_hash().unwrap()); - if address_type == BRC20_PUBKEY_ADDRESS_P2WPKH { - wpkh_script - } else { - ScriptBuf::new_p2sh(&wpkh_script.script_hash()) - } - }, - BRC20_PUBKEY_ADDRESS_P2TR_KEY => { - ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey)) - }, - _ => ScriptBuf::new(), - } -} diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index df82b65049..72aee828bb 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -14,6 +14,7 @@ mod fixed_point; mod operation; mod policies; mod ticker; +mod utils; pub static MAXIMUM_SUPPLY: Lazy = Lazy::new(|| FixedPoint::new_unchecked(u128::from(u64::MAX), 0)); @@ -32,7 +33,10 @@ pub enum BRC20Operation { op: Mint, parent: Option, }, - InscribeTransfer(Transfer), + InscribeTransfer { + signer: Option, + transfer: Transfer, + }, Transfer { ticker: BRC20Ticker, amount: u128, @@ -61,6 +65,7 @@ pub struct CreatedInscription<'a> { pub new_satpoint: SatPoint, pub pre_jubilant_curse_reason: Option<&'a Curse>, pub charms: u16, + pub tapscript_pk: [u8; 35], } impl CreatedInscription<'_> { @@ -77,6 +82,7 @@ impl<'a> From<&'a OkxInscriptionEvent> for Option> { parents, pre_jubilant_curse_reason, charms, + tapscript_pk, .. } => Some(CreatedInscription { txid: event.txid, @@ -88,6 +94,7 @@ impl<'a> From<&'a OkxInscriptionEvent> for Option> { new_satpoint: event.new_satpoint, pre_jubilant_curse_reason: pre_jubilant_curse_reason.as_ref(), charms: *charms, + tapscript_pk: *tapscript_pk, }), _ => None, } @@ -138,7 +145,19 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { op: mint, parent: self.parents.first().cloned(), }), - Ok(RawOperation::Transfer(transfer)) => Some(BRC20Operation::InscribeTransfer(transfer)), + Ok(RawOperation::Transfer(transfer)) => { + let address_type = self.tapscript_pk[34]; + let signer = if address_type > 0 { + let script = utils::get_pk_script_by_pubkey_and_type(&self.tapscript_pk[1..33], address_type); + Some(UtxoAddress::from_script(script.as_script(), &chain)) + } else { + None + }; + Some(BRC20Operation::InscribeTransfer{ + signer, + transfer, + }) + } _ => None, } } else { diff --git a/src/okx/brc20/event.rs b/src/okx/brc20/event.rs index 8d180917b3..6d327ed7b7 100644 --- a/src/okx/brc20/event.rs +++ b/src/okx/brc20/event.rs @@ -14,7 +14,7 @@ impl From<&BRC20Operation> for BRC20OpType { match value { BRC20Operation::Deploy(_) => BRC20OpType::Deploy, BRC20Operation::Mint { .. } => BRC20OpType::Mint, - BRC20Operation::InscribeTransfer(_) => BRC20OpType::InscribeTransfer, + BRC20Operation::InscribeTransfer{ .. } => BRC20OpType::InscribeTransfer, BRC20Operation::Transfer { .. } => BRC20OpType::Transfer, } } diff --git a/src/okx/brc20/executor.rs b/src/okx/brc20/executor.rs index 2dfb61b112..2aca199782 100644 --- a/src/okx/brc20/executor.rs +++ b/src/okx/brc20/executor.rs @@ -21,7 +21,6 @@ pub(crate) struct BRC20ExecutionMessage { sender: UtxoAddress, receiver: Option, // no address, if unbound operation: BRC20Operation, - signer: Option, } impl BRC20ExecutionMessage { @@ -29,7 +28,7 @@ impl BRC20ExecutionMessage { value: &BundleMessage, context: &mut TableContext, ) -> Result> { - let build_message = |operation, signer| { + let build_message = |operation| { Ok(Some(Self { txid: value.txid, inscription_id: value.inscription_id, @@ -39,15 +38,14 @@ impl BRC20ExecutionMessage { new_satpoint: value.new_satpoint, sender: value.sender.clone(), receiver: value.receiver.clone(), - signer: signer, operation, })) }; match &value.inscription_action { - InscriptionAction::Created { sub_type, signer, .. } => { + InscriptionAction::Created { sub_type, .. } => { if let Some(SubType::BRC20(brc20_operation)) = sub_type { - build_message(brc20_operation.clone(), signer.clone()) + build_message(brc20_operation.clone()) } else { Ok(None) } @@ -55,7 +53,7 @@ impl BRC20ExecutionMessage { InscriptionAction::Transferred => match Option::::from(value) { Some(transferred_inscription) => { match transferred_inscription.extract_and_validate_transfer(context) { - Ok(Some(brc20_operation)) => build_message(brc20_operation, None), + Ok(Some(brc20_operation)) => build_message(brc20_operation), Ok(None) => Ok(None), Err(err) => Err(err), } @@ -76,7 +74,7 @@ impl BRC20ExecutionMessage { let result = match &self.operation { BRC20Operation::Deploy(..) => self.execute_deploy(context, height, blocktime), BRC20Operation::Mint { .. } => self.execute_mint(context, height), - BRC20Operation::InscribeTransfer(_) => self.execute_inscribe_transfer(context), + BRC20Operation::InscribeTransfer{ .. } => self.execute_inscribe_transfer(context), BRC20Operation::Transfer { .. } => self.execute_transfer(context), }; diff --git a/src/okx/brc20/executor/inscribe_transfer.rs b/src/okx/brc20/executor/inscribe_transfer.rs index 9ee0b8d638..faae202136 100644 --- a/src/okx/brc20/executor/inscribe_transfer.rs +++ b/src/okx/brc20/executor/inscribe_transfer.rs @@ -5,7 +5,7 @@ impl BRC20ExecutionMessage { &self, context: &mut TableContext, ) -> Result { - let BRC20Operation::InscribeTransfer(transfer) = &self.operation else { + let BRC20Operation::InscribeTransfer{ signer, transfer } = &self.operation else { unreachable!() }; @@ -35,14 +35,14 @@ impl BRC20ExecutionMessage { .load_brc20_balance(&sender, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); - if sender_balance.single_step_transfer && self.signer == None { + if sender_balance.single_step_transfer && signer.is_none() { return Err(ExecutionError::ExecutionFailed( BRC20Error::LegacyTransferPermissionDenied, )); } let mut receiver_balance: Option = None; - if let Some(signer) = self.signer.clone() { + if let Some(signer) = signer.clone() { sender_or_legacy = signer.clone(); if signer != sender { receiver_balance = Some(sender_balance); diff --git a/src/okx/brc20/utils.rs b/src/okx/brc20/utils.rs new file mode 100644 index 0000000000..c5e7c497dd --- /dev/null +++ b/src/okx/brc20/utils.rs @@ -0,0 +1,96 @@ +use { + bitcoin::{ + secp256k1::{XOnlyPublicKey}, + script::{ScriptBuf}, + key::{TweakedPublicKey}, + }, +}; + +const BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT: u8 = 0x51; +const BRC20_PUBKEY_ADDRESS_P2PKH_EVEN: u8 = 0x52; +const BRC20_PUBKEY_ADDRESS_P2PKH_ODD: u8 = 0x53; +const BRC20_PUBKEY_ADDRESS_P2WPKH: u8 = 0x54; +const BRC20_PUBKEY_ADDRESS_P2TR_KEY: u8 = 0x55; +const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH: u8 = 0x56; + +/// Get a script pubkey based on the provided pubkey and address type +pub fn get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes: &[u8], address_type: u8) -> ScriptBuf { + let x_only_pubkey = XOnlyPublicKey::from_slice(x_only_pubkey_bytes).unwrap(); + match address_type { + BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT => { + let secp = bitcoin::secp256k1::Secp256k1::verification_only(); + ScriptBuf::new_p2tr(&secp, x_only_pubkey, None) + }, + BRC20_PUBKEY_ADDRESS_P2PKH_EVEN | BRC20_PUBKEY_ADDRESS_P2PKH_ODD => { + let parity = if address_type == BRC20_PUBKEY_ADDRESS_P2PKH_EVEN { + bitcoin::secp256k1::Parity::Even + } else { + bitcoin::secp256k1::Parity::Odd + }; + let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(parity)); + ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()) + }, + BRC20_PUBKEY_ADDRESS_P2WPKH | BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH => { + let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(bitcoin::secp256k1::Parity::Even)); + let wpkh_script = ScriptBuf::new_p2wpkh(&pubkey.wpubkey_hash().unwrap()); + if address_type == BRC20_PUBKEY_ADDRESS_P2WPKH { + wpkh_script + } else { + ScriptBuf::new_p2sh(&wpkh_script.script_hash()) + } + }, + BRC20_PUBKEY_ADDRESS_P2TR_KEY => { + ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey)) + }, + _ => ScriptBuf::new(), + } +} + +#[cfg(test)] +mod tests { + use super::{ + get_pk_script_by_pubkey_and_type, + BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT, + BRC20_PUBKEY_ADDRESS_P2PKH_EVEN, + BRC20_PUBKEY_ADDRESS_P2PKH_ODD, + BRC20_PUBKEY_ADDRESS_P2WPKH, + BRC20_PUBKEY_ADDRESS_P2TR_KEY, + BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH, + }; + use hex::FromHex; + use { + bitcoin::{ + secp256k1::{XOnlyPublicKey}, + }, + }; + + #[test] + fn test_script() { + let pubkey_hex = "50929b74c1a04954b78b4b6035e97a5e078a5b3f50a59849cd485efb9a3a8d9b"; + let pubkey_bytes = >::from_hex(pubkey_hex).unwrap(); + let x_only_pubkey_bytes = &XOnlyPublicKey::from_slice(&pubkey_bytes).unwrap().serialize(); + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT).as_script().to_string(); + debug_assert_eq!(script, "OP_PUSHNUM_1 OP_PUSHBYTES_32 3f5d684aefca2c2dfe2a15e73da9baee46abd0565db698d774351dddf869c20b"); + + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2PKH_EVEN).as_script().to_string(); + debug_assert_eq!(script, "OP_DUP OP_HASH160 OP_PUSHBYTES_20 f2cf7388606c1115b219e1de85f369aca3381ea2 OP_EQUALVERIFY OP_CHECKSIG"); + + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2PKH_ODD).as_script().to_string(); + debug_assert_eq!(script, "OP_DUP OP_HASH160 OP_PUSHBYTES_20 89ea44e198e0bb923ad5ca2300eda835cf9cf4c2 OP_EQUALVERIFY OP_CHECKSIG"); + + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2WPKH).as_script().to_string(); + debug_assert_eq!(script, "OP_0 OP_PUSHBYTES_20 f2cf7388606c1115b219e1de85f369aca3381ea2"); + + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2TR_KEY).as_script().to_string(); + debug_assert_eq!(script, "OP_PUSHNUM_1 OP_PUSHBYTES_32 50929b74c1a04954b78b4b6035e97a5e078a5b3f50a59849cd485efb9a3a8d9b"); + + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH).as_script().to_string(); + debug_assert_eq!(script, "OP_HASH160 OP_PUSHBYTES_20 9fe13f2414b5e0c7c6929f07d4eab1be3ebc452a OP_EQUAL"); + } +} From 0e4ad93bf3e259cf525bf8f7808166ff6a993f50 Mon Sep 17 00:00:00 2001 From: nandhh Date: Fri, 28 Mar 2025 14:27:19 +0800 Subject: [PATCH 03/12] fix: single step transfer support vendicated inscription --- src/okx/brc20.rs | 23 +++++++++++++++++++---- src/okx/brc20/policies.rs | 11 +++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index 72aee828bb..18de6d580f 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -114,8 +114,13 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { self.charms, self.pre_jubilant_curse_reason, ) { + let vindicated_set = HardForks::check_inscription_vindicated(self.charms); + match self.inscription.extract_brc20_operation() { Ok(RawOperation::Deploy(mut deploy)) => { + if vindicated_set { + return None; + } // Filter out invalid deployments with a 5-byte ticker. // proposal for issuance self mint token. // https://l1f.discourse.group/t/brc-20-proposal-for-issuance-and-burn-enhancements-brc20-ip-1/621 @@ -141,10 +146,15 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { } Some(BRC20Operation::Deploy(deploy)) } - Ok(RawOperation::Mint(mint)) => Some(BRC20Operation::Mint { - op: mint, - parent: self.parents.first().cloned(), - }), + Ok(RawOperation::Mint(mint)) => { + if vindicated_set { + return None; + } + Some(BRC20Operation::Mint { + op: mint, + parent: self.parents.first().cloned(), + }) + } Ok(RawOperation::Transfer(transfer)) => { let address_type = self.tapscript_pk[34]; let signer = if address_type > 0 { @@ -153,6 +163,11 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { } else { None }; + + if vindicated_set && signer.is_none() { + return None; + } + Some(BRC20Operation::InscribeTransfer{ signer, transfer, diff --git a/src/okx/brc20/policies.rs b/src/okx/brc20/policies.rs index dbb90882fe..bf1c56bf29 100644 --- a/src/okx/brc20/policies.rs +++ b/src/okx/brc20/policies.rs @@ -26,6 +26,10 @@ impl HardForks { } } + pub fn check_inscription_vindicated(charms: u16) -> bool { + Charm::Vindicated.is_set(charms) + } + /// Check if the inscription preconditions are met for the given curse, charms, height, and chain. pub fn check_inscription_preconditions( height: u32, @@ -38,13 +42,12 @@ impl HardForks { return false; } - let vindicated_set = Charm::Vindicated.is_set(charms); let below_activation_height = height < Self::draft_reinscription_activation_height(chain); - if below_activation_height { - !vindicated_set + true } else { - !vindicated_set || matches!(pre_jubilant_curse_reason, Some(Curse::Reinscription)) + // fixme: check vindicated + matches!(pre_jubilant_curse_reason, Some(Curse::Reinscription)) } } } From 64bca8a1e0470c9b9e760e8ec08ae98cffaa2f3b Mon Sep 17 00:00:00 2001 From: nandhh Date: Sat, 29 Mar 2025 07:58:34 +0800 Subject: [PATCH 04/12] fix: not enable single step transfer if balance insufficient --- src/okx/brc20/executor/inscribe_transfer.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/okx/brc20/executor/inscribe_transfer.rs b/src/okx/brc20/executor/inscribe_transfer.rs index faae202136..c92a3abf75 100644 --- a/src/okx/brc20/executor/inscribe_transfer.rs +++ b/src/okx/brc20/executor/inscribe_transfer.rs @@ -35,7 +35,8 @@ impl BRC20ExecutionMessage { .load_brc20_balance(&sender, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); - if sender_balance.single_step_transfer && signer.is_none() { + let single_step_transfer = signer.is_some(); + if sender_balance.single_step_transfer && !single_step_transfer { return Err(ExecutionError::ExecutionFailed( BRC20Error::LegacyTransferPermissionDenied, )); @@ -52,11 +53,6 @@ impl BRC20ExecutionMessage { .load_brc20_balance(&sender, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); } - - if !sender_balance.single_step_transfer { - sender_balance.single_step_transfer = true; - context.update_brc20_balance(&sender, &ticker, sender_balance.clone())?; - } } let available = FixedPoint::new_unchecked(sender_balance.available, ticker_info.decimals); @@ -69,7 +65,6 @@ impl BRC20ExecutionMessage { .0; let amount = amt.to_u128_and_scale().0; - if let Some(mut receiver_balance) = receiver_balance { sender_balance.total = sender_balance .total @@ -84,6 +79,9 @@ impl BRC20ExecutionMessage { context.update_brc20_balance(&receiver, &ticker, receiver_balance)?; } + if single_step_transfer { + sender_balance.single_step_transfer = true; + } context.update_brc20_balance(&sender, &ticker, sender_balance)?; let transferring_asset = BRC20TransferAsset { From 93d419d19ee3d8ce03f03dc5ae0cba10f130bf11 Mon Sep 17 00:00:00 2001 From: nandhh Date: Sat, 29 Mar 2025 11:11:42 +0800 Subject: [PATCH 05/12] feat: set activation height --- src/okx/brc20.rs | 14 ++++++++------ src/okx/brc20/policies.rs | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index 18de6d580f..8b72ef8a4a 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -156,18 +156,20 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { }) } Ok(RawOperation::Transfer(transfer)) => { - let address_type = self.tapscript_pk[34]; + let address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { + self.tapscript_pk[34] + }else { + 0 + }; + if vindicated_set && address_type == 0 { + return None; + } let signer = if address_type > 0 { let script = utils::get_pk_script_by_pubkey_and_type(&self.tapscript_pk[1..33], address_type); Some(UtxoAddress::from_script(script.as_script(), &chain)) } else { None }; - - if vindicated_set && signer.is_none() { - return None; - } - Some(BRC20Operation::InscribeTransfer{ signer, transfer, diff --git a/src/okx/brc20/policies.rs b/src/okx/brc20/policies.rs index bf1c56bf29..6500008804 100644 --- a/src/okx/brc20/policies.rs +++ b/src/okx/brc20/policies.rs @@ -16,6 +16,16 @@ impl HardForks { } } + pub fn self_single_step_transfer_activation_height(chain: &Chain) -> u32 { + match chain { + Chain::Mainnet => 895090, // decided by community + Chain::Testnet => 2413343, // + Chain::Regtest => 0, + Chain::Signet => 0, + Chain::Testnet4 => 0, + } + } + pub fn draft_reinscription_activation_height(chain: &Chain) -> u32 { match chain { Chain::Mainnet => u32::MAX, // todo: not set yet From 082c563f85ff56f36b9e371e90c5b145d6e38078 Mon Sep 17 00:00:00 2001 From: nandhh Date: Tue, 8 Apr 2025 11:46:39 +0800 Subject: [PATCH 06/12] fix: single-step transfer enable height --- src/okx/brc20.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index 8b72ef8a4a..f69a1e3acb 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -115,6 +115,17 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { self.pre_jubilant_curse_reason, ) { let vindicated_set = HardForks::check_inscription_vindicated(self.charms); + let address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { + 0 + }else { + self.tapscript_pk[34] + }; + let signer = if address_type > 0 { + let script = utils::get_pk_script_by_pubkey_and_type(&self.tapscript_pk[1..33], address_type); + Some(UtxoAddress::from_script(script.as_script(), &chain)) + } else { + None + }; match self.inscription.extract_brc20_operation() { Ok(RawOperation::Deploy(mut deploy)) => { @@ -156,21 +167,10 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { }) } Ok(RawOperation::Transfer(transfer)) => { - let address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { - self.tapscript_pk[34] - }else { - 0 - }; if vindicated_set && address_type == 0 { return None; } - let signer = if address_type > 0 { - let script = utils::get_pk_script_by_pubkey_and_type(&self.tapscript_pk[1..33], address_type); - Some(UtxoAddress::from_script(script.as_script(), &chain)) - } else { - None - }; - Some(BRC20Operation::InscribeTransfer{ + Some(BRC20Operation::InscribeTransfer { signer, transfer, }) From d78be55e2eb1fbbac03bd673050c92c25aa9fe96 Mon Sep 17 00:00:00 2001 From: nandhh Date: Tue, 8 Apr 2025 11:47:23 +0800 Subject: [PATCH 07/12] feat: support brc20 clean mint --- src/okx/brc20.rs | 2 ++ src/okx/brc20/executor/mint.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index f69a1e3acb..fa3445908e 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -31,6 +31,7 @@ pub enum BRC20Operation { Deploy(Deploy), Mint { op: Mint, + signer: Option, parent: Option, }, InscribeTransfer { @@ -163,6 +164,7 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { } Some(BRC20Operation::Mint { op: mint, + signer, parent: self.parents.first().cloned(), }) } diff --git a/src/okx/brc20/executor/mint.rs b/src/okx/brc20/executor/mint.rs index 41f34b9057..0d1562b001 100644 --- a/src/okx/brc20/executor/mint.rs +++ b/src/okx/brc20/executor/mint.rs @@ -6,7 +6,7 @@ impl BRC20ExecutionMessage { context: &mut TableContext, height: u32, ) -> Result { - let BRC20Operation::Mint { op: mint, parent } = &self.operation else { + let BRC20Operation::Mint { op: mint, signer, parent } = &self.operation else { unreachable!() }; @@ -61,7 +61,12 @@ impl BRC20ExecutionMessage { } // get user's balances - let receiver = self.receiver.clone().unwrap(); + let receiver = if let Some(signer) = signer.clone() { + signer + } else { + self.receiver.clone().unwrap() + }; + let mut receiver_balance = context .load_brc20_balance(&receiver, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); From ebf10b4bd51f335c87b8923100064995d7b69044 Mon Sep 17 00:00:00 2001 From: nandhh Date: Fri, 11 Apr 2025 17:05:26 +0800 Subject: [PATCH 08/12] feat: keep legacy transfer available as before --- src/okx/brc20/entry.rs | 2 -- src/okx/brc20/executor/inscribe_transfer.rs | 10 ---------- src/subcommand/server/okx/brc20/balance.rs | 3 --- 3 files changed, 15 deletions(-) diff --git a/src/okx/brc20/entry.rs b/src/okx/brc20/entry.rs index 553075e207..82327545ca 100644 --- a/src/okx/brc20/entry.rs +++ b/src/okx/brc20/entry.rs @@ -8,7 +8,6 @@ pub struct BRC20Balance { pub ticker: BRC20Ticker, pub total: u128, pub available: u128, - pub single_step_transfer: bool, } impl BRC20Balance { @@ -17,7 +16,6 @@ impl BRC20Balance { ticker: ticker.clone(), total: 0, available: 0, - single_step_transfer: false, } } } diff --git a/src/okx/brc20/executor/inscribe_transfer.rs b/src/okx/brc20/executor/inscribe_transfer.rs index c92a3abf75..0eda31470d 100644 --- a/src/okx/brc20/executor/inscribe_transfer.rs +++ b/src/okx/brc20/executor/inscribe_transfer.rs @@ -35,13 +35,6 @@ impl BRC20ExecutionMessage { .load_brc20_balance(&sender, &ticker)? .unwrap_or(BRC20Balance::new_with_ticker(&ticker)); - let single_step_transfer = signer.is_some(); - if sender_balance.single_step_transfer && !single_step_transfer { - return Err(ExecutionError::ExecutionFailed( - BRC20Error::LegacyTransferPermissionDenied, - )); - } - let mut receiver_balance: Option = None; if let Some(signer) = signer.clone() { sender_or_legacy = signer.clone(); @@ -79,9 +72,6 @@ impl BRC20ExecutionMessage { context.update_brc20_balance(&receiver, &ticker, receiver_balance)?; } - if single_step_transfer { - sender_balance.single_step_transfer = true; - } context.update_brc20_balance(&sender, &ticker, sender_balance)?; let transferring_asset = BRC20TransferAsset { diff --git a/src/subcommand/server/okx/brc20/balance.rs b/src/subcommand/server/okx/brc20/balance.rs index 063c7f4b84..6fab520d32 100644 --- a/src/subcommand/server/okx/brc20/balance.rs +++ b/src/subcommand/server/okx/brc20/balance.rs @@ -7,7 +7,6 @@ pub struct ApiBalance { pub available_balance: String, pub transferable_balance: String, pub overall_balance: String, - pub single_step_transfer: bool, } /// Get the ticker balance of the address. @@ -46,7 +45,6 @@ pub(crate) async fn brc20_balance( available_balance: balance.available.to_string(), transferable_balance: (balance.total - balance.available).to_string(), overall_balance: balance.total.to_string(), - single_step_transfer: balance.single_step_transfer, }))) }) } @@ -83,7 +81,6 @@ pub(crate) async fn brc20_all_balance( available_balance: balance.available.to_string(), transferable_balance: (balance.total - balance.available).to_string(), overall_balance: balance.total.to_string(), - single_step_transfer: balance.single_step_transfer, }) .collect(), }))) From 71041acc6ee4a8672342359c36b42d157960d7c5 Mon Sep 17 00:00:00 2001 From: nandhh Date: Fri, 11 Apr 2025 18:37:00 +0800 Subject: [PATCH 09/12] feat: enable single-step transfer on 5b-ticker first --- src/okx/brc20.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index fa3445908e..50370807dd 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -116,12 +116,12 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { self.pre_jubilant_curse_reason, ) { let vindicated_set = HardForks::check_inscription_vindicated(self.charms); - let address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { + let mut address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { 0 }else { self.tapscript_pk[34] }; - let signer = if address_type > 0 { + let mut signer = if address_type > 0 { let script = utils::get_pk_script_by_pubkey_and_type(&self.tapscript_pk[1..33], address_type); Some(UtxoAddress::from_script(script.as_script(), &chain)) } else { @@ -162,6 +162,9 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { if vindicated_set { return None; } + if mint.tick.len() != SELF_ISSUANCE_TICKER_LENGTH { + signer = None; + } Some(BRC20Operation::Mint { op: mint, signer, @@ -169,6 +172,10 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { }) } Ok(RawOperation::Transfer(transfer)) => { + if transfer.tick.len() != SELF_ISSUANCE_TICKER_LENGTH { + signer = None; + address_type = 0; + } if vindicated_set && address_type == 0 { return None; } From df6ae302438a6af3073fa4ee136befbc4322438a Mon Sep 17 00:00:00 2001 From: nandhh Date: Sat, 19 Apr 2025 15:15:50 +0800 Subject: [PATCH 10/12] fix: support 8 types of addresses --- src/index/updater/inscription_updater.rs | 2 +- src/okx/brc20/utils.rs | 59 +++++++++++++----------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index b5fd84328a..d2d2467546 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -164,7 +164,7 @@ impl InscriptionUpdater<'_, '_> { if script_bytes[0] == opcodes::all::OP_PUSHBYTES_32.to_u8() && script_bytes[33] == opcodes::all::OP_CHECKSIGVERIFY.to_u8() && script_bytes[34] >= opcodes::all::OP_PUSHNUM_1.to_u8() - && script_bytes[34] <= opcodes::all::OP_PUSHNUM_6.to_u8() { + && script_bytes[34] <= opcodes::all::OP_PUSHNUM_8.to_u8() { tapscript_pk.copy_from_slice(&script_bytes[..35]); } } diff --git a/src/okx/brc20/utils.rs b/src/okx/brc20/utils.rs index c5e7c497dd..61a8b48b0a 100644 --- a/src/okx/brc20/utils.rs +++ b/src/okx/brc20/utils.rs @@ -7,37 +7,37 @@ use { }; const BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT: u8 = 0x51; -const BRC20_PUBKEY_ADDRESS_P2PKH_EVEN: u8 = 0x52; -const BRC20_PUBKEY_ADDRESS_P2PKH_ODD: u8 = 0x53; -const BRC20_PUBKEY_ADDRESS_P2WPKH: u8 = 0x54; -const BRC20_PUBKEY_ADDRESS_P2TR_KEY: u8 = 0x55; -const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH: u8 = 0x56; +const BRC20_PUBKEY_ADDRESS_P2WPKH_EVEN: u8 = 0x52; +const BRC20_PUBKEY_ADDRESS_P2WPKH_ODD: u8 = 0x53; +const BRC20_PUBKEY_ADDRESS_P2PKH_EVEN: u8 = 0x54; +const BRC20_PUBKEY_ADDRESS_P2PKH_ODD: u8 = 0x55; +const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_EVEN: u8 = 0x56; +const BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_ODD: u8 = 0x57; +const BRC20_PUBKEY_ADDRESS_P2TR_KEY: u8 = 0x58; /// Get a script pubkey based on the provided pubkey and address type pub fn get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes: &[u8], address_type: u8) -> ScriptBuf { let x_only_pubkey = XOnlyPublicKey::from_slice(x_only_pubkey_bytes).unwrap(); + let parity = if address_type == BRC20_PUBKEY_ADDRESS_P2PKH_EVEN || address_type == BRC20_PUBKEY_ADDRESS_P2WPKH_EVEN || address_type == BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_EVEN { + bitcoin::secp256k1::Parity::Even + } else { + bitcoin::secp256k1::Parity::Odd + }; + let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(parity)); match address_type { BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT => { let secp = bitcoin::secp256k1::Secp256k1::verification_only(); ScriptBuf::new_p2tr(&secp, x_only_pubkey, None) }, + BRC20_PUBKEY_ADDRESS_P2WPKH_EVEN | BRC20_PUBKEY_ADDRESS_P2WPKH_ODD => { + ScriptBuf::new_p2wpkh(&pubkey.wpubkey_hash().unwrap()) + }, BRC20_PUBKEY_ADDRESS_P2PKH_EVEN | BRC20_PUBKEY_ADDRESS_P2PKH_ODD => { - let parity = if address_type == BRC20_PUBKEY_ADDRESS_P2PKH_EVEN { - bitcoin::secp256k1::Parity::Even - } else { - bitcoin::secp256k1::Parity::Odd - }; - let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(parity)); ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()) }, - BRC20_PUBKEY_ADDRESS_P2WPKH | BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH => { - let pubkey = bitcoin::PublicKey::new(x_only_pubkey.public_key(bitcoin::secp256k1::Parity::Even)); + BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_EVEN | BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_ODD => { let wpkh_script = ScriptBuf::new_p2wpkh(&pubkey.wpubkey_hash().unwrap()); - if address_type == BRC20_PUBKEY_ADDRESS_P2WPKH { - wpkh_script - } else { - ScriptBuf::new_p2sh(&wpkh_script.script_hash()) - } + ScriptBuf::new_p2sh(&wpkh_script.script_hash()) }, BRC20_PUBKEY_ADDRESS_P2TR_KEY => { ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey)) @@ -51,11 +51,13 @@ mod tests { use super::{ get_pk_script_by_pubkey_and_type, BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT, + BRC20_PUBKEY_ADDRESS_P2WPKH_EVEN, + BRC20_PUBKEY_ADDRESS_P2WPKH_ODD, BRC20_PUBKEY_ADDRESS_P2PKH_EVEN, BRC20_PUBKEY_ADDRESS_P2PKH_ODD, - BRC20_PUBKEY_ADDRESS_P2WPKH, + BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_EVEN, + BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_ODD, BRC20_PUBKEY_ADDRESS_P2TR_KEY, - BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH, }; use hex::FromHex; use { @@ -73,24 +75,25 @@ mod tests { let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2TR_SCRIPT).as_script().to_string(); debug_assert_eq!(script, "OP_PUSHNUM_1 OP_PUSHBYTES_32 3f5d684aefca2c2dfe2a15e73da9baee46abd0565db698d774351dddf869c20b"); + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2WPKH_EVEN).as_script().to_string(); + debug_assert_eq!(script, "OP_0 OP_PUSHBYTES_20 f2cf7388606c1115b219e1de85f369aca3381ea2"); + + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2WPKH_ODD).as_script().to_string(); + debug_assert_eq!(script, "OP_0 OP_PUSHBYTES_20 89ea44e198e0bb923ad5ca2300eda835cf9cf4c2"); let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2PKH_EVEN).as_script().to_string(); debug_assert_eq!(script, "OP_DUP OP_HASH160 OP_PUSHBYTES_20 f2cf7388606c1115b219e1de85f369aca3381ea2 OP_EQUALVERIFY OP_CHECKSIG"); - let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2PKH_ODD).as_script().to_string(); debug_assert_eq!(script, "OP_DUP OP_HASH160 OP_PUSHBYTES_20 89ea44e198e0bb923ad5ca2300eda835cf9cf4c2 OP_EQUALVERIFY OP_CHECKSIG"); + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_EVEN).as_script().to_string(); + debug_assert_eq!(script, "OP_HASH160 OP_PUSHBYTES_20 9fe13f2414b5e0c7c6929f07d4eab1be3ebc452a OP_EQUAL"); - let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2WPKH).as_script().to_string(); - debug_assert_eq!(script, "OP_0 OP_PUSHBYTES_20 f2cf7388606c1115b219e1de85f369aca3381ea2"); - + let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH_ODD).as_script().to_string(); + debug_assert_eq!(script, "OP_HASH160 OP_PUSHBYTES_20 0eb6f1c05f8e8b7f7aa8952e2ce64aa7f050215b OP_EQUAL"); let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2TR_KEY).as_script().to_string(); debug_assert_eq!(script, "OP_PUSHNUM_1 OP_PUSHBYTES_32 50929b74c1a04954b78b4b6035e97a5e078a5b3f50a59849cd485efb9a3a8d9b"); - - - let script = get_pk_script_by_pubkey_and_type(x_only_pubkey_bytes, BRC20_PUBKEY_ADDRESS_P2SH_P2WPKH).as_script().to_string(); - debug_assert_eq!(script, "OP_HASH160 OP_PUSHBYTES_20 9fe13f2414b5e0c7c6929f07d4eab1be3ebc452a OP_EQUAL"); } } From 70a8b68d83020cd33236e9e02d0a1cff6b10837a Mon Sep 17 00:00:00 2001 From: nandhh Date: Sat, 19 Apr 2025 19:23:00 +0800 Subject: [PATCH 11/12] feat: support reinscription --- src/okx/brc20.rs | 9 ++++----- src/okx/brc20/policies.rs | 22 +++------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index 50370807dd..b5bfaebb93 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -113,9 +113,8 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { height, &chain, self.charms, - self.pre_jubilant_curse_reason, ) { - let vindicated_set = HardForks::check_inscription_vindicated(self.charms); + let first_inscription = self.inscription_id.index == 0; let mut address_type = if height < HardForks::self_single_step_transfer_activation_height(&chain) { 0 }else { @@ -130,7 +129,7 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { match self.inscription.extract_brc20_operation() { Ok(RawOperation::Deploy(mut deploy)) => { - if vindicated_set { + if !first_inscription { return None; } // Filter out invalid deployments with a 5-byte ticker. @@ -159,7 +158,7 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { Some(BRC20Operation::Deploy(deploy)) } Ok(RawOperation::Mint(mint)) => { - if vindicated_set { + if !first_inscription { return None; } if mint.tick.len() != SELF_ISSUANCE_TICKER_LENGTH { @@ -176,7 +175,7 @@ impl BRC20CreationOperationExtractor for CreatedInscription<'_> { signer = None; address_type = 0; } - if vindicated_set && address_type == 0 { + if address_type == 0 && !first_inscription { return None; } Some(BRC20Operation::InscribeTransfer { diff --git a/src/okx/brc20/policies.rs b/src/okx/brc20/policies.rs index 6500008804..ef0b0deb18 100644 --- a/src/okx/brc20/policies.rs +++ b/src/okx/brc20/policies.rs @@ -26,38 +26,22 @@ impl HardForks { } } - pub fn draft_reinscription_activation_height(chain: &Chain) -> u32 { - match chain { - Chain::Mainnet => u32::MAX, // todo: not set yet - Chain::Testnet => u32::MAX, - Chain::Regtest => u32::MAX, - Chain::Signet => u32::MAX, - Chain::Testnet4 => u32::MAX, - } - } - - pub fn check_inscription_vindicated(charms: u16) -> bool { - Charm::Vindicated.is_set(charms) - } - /// Check if the inscription preconditions are met for the given curse, charms, height, and chain. pub fn check_inscription_preconditions( height: u32, chain: &Chain, charms: u16, - pre_jubilant_curse_reason: Option<&Curse>, ) -> bool { // can not be unbound or cursed if Charm::Unbound.is_set(charms) || Charm::Cursed.is_set(charms) { return false; } - let below_activation_height = height < Self::draft_reinscription_activation_height(chain); + let below_activation_height = height < Self::self_single_step_transfer_activation_height(chain); if below_activation_height { - true + !Charm::Vindicated.is_set(charms) } else { - // fixme: check vindicated - matches!(pre_jubilant_curse_reason, Some(Curse::Reinscription)) + true } } } From e45e1971e4ef84efe56f5b94177e230ced8687a8 Mon Sep 17 00:00:00 2001 From: nandhh Date: Sat, 19 Apr 2025 19:28:53 +0800 Subject: [PATCH 12/12] feat: remove noused pre_jubilant_curse_reason --- src/index/event.rs | 1 - src/index/updater/inscription_updater.rs | 4 ---- src/okx/brc20.rs | 3 --- 3 files changed, 8 deletions(-) diff --git a/src/index/event.rs b/src/index/event.rs index dc888a6ab9..36165d62e9 100644 --- a/src/index/event.rs +++ b/src/index/event.rs @@ -49,7 +49,6 @@ pub(crate) enum Action { Created { inscription: Inscription, parents: Vec, - pre_jubilant_curse_reason: Option, charms: u16, tapscript_pk: [u8; 35], }, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index d2d2467546..d780188695 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -38,7 +38,6 @@ enum Origin { unbound: bool, vindicated: bool, inscription: Inscription, - pre_jubilant_curse_reason: Option, tapscript_pk: [u8; 35], }, Old { @@ -247,7 +246,6 @@ impl InscriptionUpdater<'_, '_> { || inscription.payload.unrecognized_even_field, vindicated: curse.is_some() && jubilant, inscription: inscription.payload, - pre_jubilant_curse_reason: curse, tapscript_pk: tapscript_pk, }, }); @@ -635,13 +633,11 @@ impl InscriptionUpdater<'_, '_> { Origin::New { inscription, parents, - pre_jubilant_curse_reason, tapscript_pk, .. } => Action::Created { inscription, parents, - pre_jubilant_curse_reason, charms: charms.unwrap(), tapscript_pk, }, diff --git a/src/okx/brc20.rs b/src/okx/brc20.rs index b5bfaebb93..0c1b698613 100644 --- a/src/okx/brc20.rs +++ b/src/okx/brc20.rs @@ -64,7 +64,6 @@ pub struct CreatedInscription<'a> { pub inscription_number: i32, pub parents: &'a Vec, pub new_satpoint: SatPoint, - pub pre_jubilant_curse_reason: Option<&'a Curse>, pub charms: u16, pub tapscript_pk: [u8; 35], } @@ -81,7 +80,6 @@ impl<'a> From<&'a OkxInscriptionEvent> for Option> { Action::Created { inscription, parents, - pre_jubilant_curse_reason, charms, tapscript_pk, .. @@ -93,7 +91,6 @@ impl<'a> From<&'a OkxInscriptionEvent> for Option> { inscription_number: event.inscription_number, parents: &parents, new_satpoint: event.new_satpoint, - pre_jubilant_curse_reason: pre_jubilant_curse_reason.as_ref(), charms: *charms, tapscript_pk: *tapscript_pk, }),