diff --git a/src/chainstate/stacks/transaction.rs b/src/chainstate/stacks/transaction.rs
new file mode 100644
index 000000000..674cb01c9
--- /dev/null
+++ b/src/chainstate/stacks/transaction.rs
@@ -0,0 +1,175 @@
+/*
+ copyright: (c) 2013-2019 by Blockstack PBC, a public benefit corporation.
+
+ This file is part of Blockstack.
+
+ Blockstack is free software. You may redistribute or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License or
+ (at your option) any later version.
+
+ Blockstack is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY, including without the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Blockstack. If not, see .
+*/
+
+use net::StacksMessageCodec;
+use net::Error as net_error;
+use net::codec::{read_next, write_next};
+
+use burnchains::Txid;
+
+use chainstate::stacks::StacksAddress;
+use chainstate::stacks::TransactionAuth;
+use chainstate::stacks::TransactionAnchorMode;
+use chainstate::stacks::TransactionPayloadID;
+use chainstate::stacks::TransactionPayload;
+use chainstate::stacks::TransactionPayment;
+use chainstate::stacks::TransactionSmartContract;
+use chainstate::stacks::TransactionSmartContractCall;
+use chainstate::stacks::StacksTransaction;
+
+use util::hash::DoubleSha256;
+
+impl StacksMessageCodec for TransactionPayment {
+ fn serialize(&self) -> Vec {
+ let mut res = vec![];
+ write_next(&mut res, &self.paid);
+ write_next(&mut res, &self.recipient);
+ res
+ }
+
+ fn deserialize(buf: &Vec, index: &mut u32, max_size: u32) -> Result {
+ let paid : u64 = read_next(buf, index, max_size)?;
+ let recipient : StacksAddress = read_next(buf, index, max_size)?;
+
+ Ok(TransactionPayment {
+ paid,
+ recipient
+ })
+ }
+}
+
+impl StacksMessageCodec for TransactionSmartContract {
+ fn serialize(&self) -> Vec {
+ let mut res = vec![];
+ write_next(&mut res, &self.code_body);
+ res
+ }
+
+ fn deserialize(buf: &Vec, index: &mut u32, max_size: u32) -> Result {
+ let code_body : Vec = read_next(buf, index, max_size)?;
+ Ok(TransactionSmartContract {
+ code_body
+ })
+ }
+}
+
+impl StacksMessageCodec for TransactionSmartContractCall {
+ fn serialize(&self) -> Vec {
+ let mut res = vec![];
+ write_next(&mut res, &self.code_body);
+ res
+ }
+
+ fn deserialize(buf: &Vec, index: &mut u32, max_size: u32) -> Result {
+ let code_body : Vec = read_next(buf, index, max_size)?;
+ Ok(TransactionSmartContractCall {
+ code_body
+ })
+ }
+}
+
+impl StacksMessageCodec for StacksTransaction {
+ fn serialize(&self) -> Vec {
+ let mut ret = vec![];
+ let anchor_mode = self.anchor_mode;
+
+ write_next(&mut ret, &self.version);
+ write_next(&mut ret, &self.principal);
+ write_next(&mut ret, &self.auth);
+ write_next(&mut ret, &self.fee);
+ write_next(&mut ret, &(self.anchor_mode as u8));
+
+ // payload will be formatted as "type (u8) payload (vec)"
+ let transaction_type_id : u8 =
+ match self.payload {
+ TransactionPayload::Payment(ref _t) => TransactionPayloadID::Payment as u8,
+ TransactionPayload::SmartContract(ref _t) => TransactionPayloadID::SmartContract as u8,
+ TransactionPayload::SmartContractCall(ref _t) => TransactionPayloadID::SmartContractCall as u8,
+ };
+
+ write_next(&mut ret, &transaction_type_id);
+
+ match self.payload {
+ TransactionPayload::Payment(ref t) => write_next(&mut ret, t),
+ TransactionPayload::SmartContract(ref t) => write_next(&mut ret, t),
+ TransactionPayload::SmartContractCall(ref t) => write_next(&mut ret, t)
+ };
+ ret
+ }
+
+ fn deserialize(buf: &Vec, index: &mut u32, max_size: u32) -> Result {
+ let version : u8 = read_next(buf, index, max_size)?;
+ let principal : StacksAddress = read_next(buf, index, max_size)?;
+ let auth : TransactionAuth = read_next(buf, index, max_size)?;
+ let fee : u64 = read_next(buf, index, max_size)?;
+ let transaction_anchor_id : u8 = read_next(buf, index, max_size)?;
+ let transaction_type_id : u8 = read_next(buf, index, max_size)?;
+
+ let anchor_mode =
+ if transaction_anchor_id == (TransactionAnchorMode::OffChainOnly as u8) {
+ TransactionAnchorMode::OffChainOnly
+ }
+ else if transaction_anchor_id == (TransactionAnchorMode::OnChainOnly as u8) {
+ TransactionAnchorMode::OnChainOnly
+ }
+ else if transaction_anchor_id == (TransactionAnchorMode::Any as u8) {
+ TransactionAnchorMode::Any
+ }
+ else {
+ return Err(net_error::DeserializeError);
+ };
+
+ let payload =
+ if transaction_type_id == (TransactionPayloadID::Payment as u8) {
+ let payload_data = TransactionPayment::deserialize(buf, index, max_size)?;
+ TransactionPayload::Payment(payload_data)
+ }
+ else if transaction_type_id == (TransactionPayloadID::SmartContract as u8) {
+ let payload_data = TransactionSmartContract::deserialize(buf, index, max_size)?;
+ TransactionPayload::SmartContract(payload_data)
+ }
+ else if transaction_type_id == (TransactionPayloadID::SmartContractCall as u8) {
+ let payload_data = TransactionSmartContractCall::deserialize(buf, index, max_size)?;
+ TransactionPayload::SmartContractCall(payload_data)
+ }
+ else {
+ return Err(net_error::DeserializeError);
+ };
+
+ Ok(StacksTransaction {
+ version,
+ principal,
+ auth,
+ fee,
+ anchor_mode,
+ payload
+ })
+ }
+}
+
+impl StacksTransaction {
+ /// a txid of a stacks transaction is its double-sha256 hash
+ pub fn txid(&self) -> Txid {
+ let bytes_vec = self.serialize();
+ let h = DoubleSha256::from_data(&bytes_vec[..]);
+
+ // NOTE: safe to unwrap here since a double-sha256 and a txid are both 32 bytes
+ Txid::from_bytes(h.as_bytes()).unwrap()
+ }
+}