diff --git a/CHANGELOG.md b/CHANGELOG.md index 999e019..2f357bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.2.0] + +### Added + +- Functionality to parse transparent bundles from Zebra +- Types for transparent `TxIn`, `TxOut`, `OutPoint` + ## [0.5.1.0] ### Added diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 4f99a5d..8bb4b29 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -31,9 +31,18 @@ use zcash_primitives::{ sapling::DiversifierKey }, zip339::{Count, Mnemonic}, - transaction::components::sapling::{ - GrothProofBytes, - OutputDescription + transaction::components::{ + transparent::{ + Bundle as TransparentBundle, + TxIn, + TxOut, + OutPoint, + Authorized + }, + sapling::{ + GrothProofBytes, + OutputDescription + } }, sapling::{ PaymentAddress, @@ -221,6 +230,101 @@ impl Hua { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Htx { + txid: Vec, + locktime: u32, + expiry: u32, + t_bundle: HTBundle +} + +impl ToHaskell for Htx { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTBundle { + empty: bool, + vin: Vec, + vout: Vec, + coinbase: bool +} + +impl ToHaskell for HTBundle { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTBundle { + pub fn from_bundle(b: &TransparentBundle) -> HTBundle { + HTBundle {empty: false, vin: b.vin.iter().map(HTxIn::pack).collect() , vout: b.vout.iter().map(HTxOut::pack).collect(), coinbase: b.is_coinbase()} + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTxIn { + outpoint: Houtpoint, + script: Vec, + sequence: u32 +} + +impl ToHaskell for HTxIn { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxIn { + pub fn pack(t: &TxIn) -> HTxIn { + return HTxIn { outpoint: Houtpoint::pack(&t.prevout), script: t.script_sig.0.clone(), sequence: t.sequence} + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HTxOut { + amt: i64, + script: Vec +} + +impl ToHaskell for HTxOut { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxOut { + pub fn pack(t: &TxOut) -> HTxOut { + return HTxOut { amt: i64::from_le_bytes(t.value.to_i64_le_bytes()) , script: t.script_pubkey.0.clone() } + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Houtpoint { + hash: Vec, + index: u32 +} + +impl ToHaskell for Houtpoint { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl Houtpoint { + pub fn pack(o: &OutPoint) -> Houtpoint { + return Houtpoint {hash: o.hash().to_vec() , index: o.n() } + } +} + #[derive(BorshSerialize, BorshDeserialize)] pub struct Hufvk { net: u8, @@ -556,6 +660,38 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( } } + +#[no_mangle] +pub extern "C" fn rust_wrapper_tx_read( + tx: *const u8, + tx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tx_input: Vec = marshall_from_haskell_var(tx, tx_len, RW); + let mut tx_reader = Cursor::new(tx_input); + let parsed_tx = Transaction::read(&mut tx_reader, Nu5); + match parsed_tx { + Ok(t) => { + let tb = t.transparent_bundle(); + match tb { + Some(my_tb) => { + let h = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle::from_bundle(my_tb) }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + None => { + let h0 = Htx {txid: t.txid().as_ref().to_vec(), locktime: t.lock_time(), expiry: u32::from(t.expiry_height()), t_bundle: HTBundle {empty: true, vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + }, + Err(_e) => { + let h0 = Htx {txid: vec![0], locktime: 0, expiry: 0, t_bundle: HTBundle {empty: true, vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: true} }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_tx_parse( tx: *const u8, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 19df3c3..7bf023f 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -123,6 +123,13 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_tx_read as rustWrapperTxRead + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer RawZebraTx'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_gen_seed_phrase as rustWrapperGenSeedPhrase { getVarBuffer `Buffer Phrase'& } -> `()' #} diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 612410e..ad81d41 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -16,6 +16,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE UndecidableInstances #-} @@ -29,9 +30,11 @@ import Data.Aeson import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Lazy.UTF8 as US +import qualified Data.ByteString.Short as BS (ShortByteString, toShort) import Data.HexString import Data.Int -import Data.Maybe (fromMaybe) +import Data.Maybe (fromJust, fromMaybe) import Data.Structured import qualified Data.Text as T import qualified Data.Text.Encoding as E @@ -39,7 +42,9 @@ import Data.Word import qualified GHC.Generics as GHC import qualified Generics.SOP as SOP import Haskoin.Address (Address) +import qualified Haskoin.Crypto.Hash as H (Hash256(..)) import Haskoin.Crypto.Keys.Extended (XPrvKey) +import qualified Haskoin.Transaction.Common as H -- * General -- @@ -89,6 +94,19 @@ data ZcashNet type AccountId = Int +-- | Function to get the Base58 prefix for encoding a 'TransparentAddress' +getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) +getTransparentPrefix n t = + case t of + P2SH -> + case n of + MainNet -> (0x1c, 0xbd) + _ -> (0x1c, 0xba) + P2PKH -> + case n of + MainNet -> (0x1c, 0xb8) + _ -> (0x1d, 0x25) + -- ** Constants -- | Type for coin types on the different networks data CoinType @@ -104,7 +122,50 @@ getValue c = TestNetCoin -> 1 RegTestNetCoin -> 1 --- | Constants for Sapling Human-readable part +-- | A Zcash transaction +data Transaction = Transaction + { tx_id :: !HexString + , tx_height :: !Int + , tx_conf :: !Int + , tx_expiry :: !Int + , tx_transpBundle :: !(Maybe TransparentBundle) + } deriving (Prelude.Show, Eq, Read) + +-- | The transparent portion of a Zcash transaction +data TransparentBundle = TransparentBundle + { tb_vin :: ![H.TxIn] + , tb_vout :: ![H.TxOut] + , tb_coinbase :: !Bool + } deriving (Eq, Prelude.Show, Read) + +-- | Read a raw transparent bundle into the Haskell type +fromRawTBundle :: RawTBundle -> Maybe TransparentBundle +fromRawTBundle rtb = + if ztb_empty rtb + then Nothing + else Just $ + TransparentBundle + (map fromRawTxIn $ ztb_vin rtb) + (map fromRawTxOut $ ztb_vout rtb) + (ztb_coinbase rtb) + +fromRawTxIn :: RawTxIn -> H.TxIn +fromRawTxIn t = H.TxIn op (rti_script t) (rti_seq t) + where + op = + if rop_hash (rti_outpoint t) == + "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL" + then H.nullOutPoint + else H.OutPoint + ((fromJust . + H.hexToTxHash . E.decodeUtf8Lenient . rop_hash . rti_outpoint) + t) + (rop_n $ rti_outpoint t) + +fromRawTxOut :: RawTxOut -> H.TxOut +fromRawTxOut t = H.TxOut (rto_amt t) (rto_script t) + +-- *** Constants for Sapling Human-readable part sapExtSpendingKeyHrp = "secret-extended-key-main" :: String sapExtFullViewingKeyHrp = "zxviews" :: String @@ -117,7 +178,7 @@ sapTestExtFullViewingKeyHrp = "zxviewtestsapling" :: String sapTestPaymentAddressHrp = "ztestsapling" :: String --- | Constants for Unified Human-readable part +-- *** Constants for Unified Human-readable part uniPaymentAddressHrp = "u" :: T.Text uniFullViewingKeyHrp = "uview" :: T.Text @@ -130,19 +191,6 @@ uniTestFullViewingKeyHrp = "uviewtest" :: T.Text uniTestIncomingViewingKeyHrp = "uivktest" :: T.Text --- | Function to get the Base58 prefix for encoding a 'TransparentAddress' -getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) -getTransparentPrefix n t = - case t of - P2SH -> - case n of - MainNet -> (0x1c, 0xbd) - _ -> (0x1c, 0xba) - P2PKH -> - case n of - MainNet -> (0x1c, 0xb8) - _ -> (0x1d, 0x25) - -- * RPC -- | A type to model Zcash RPC calls data RpcCall = RpcCall @@ -217,6 +265,42 @@ data RawTxResponse = RawTxResponse } deriving (Prelude.Show, Eq, Read) -- ** `zebrad` +data ZebraTxResponse = ZebraTxResponse + { ztr_blockheight :: !Int + , ztr_conf :: !Int + , ztr_hex :: !HexString + } deriving (Prelude.Show, Eq, Read) + +instance FromJSON ZebraTxResponse where + parseJSON = + withObject "ZebraTxResponse" $ \obj -> do + hex <- obj .: "hex" + height <- obj .: "height" + c <- obj .: "confirmations" + pure $ ZebraTxResponse height c hex + +-- | Type to represent a raw deserialized Zebra transaction +data RawZebraTx = RawZebraTx + { zt_id :: !HexString + , zt_locktime :: !Word32 + , zt_expiry :: !Word32 + , zt_tBundle :: !RawTBundle + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawZebraTx + +-- | Type for a raw deserialized Zebra transparent bundle +data RawTBundle = RawTBundle + { ztb_empty :: !Bool + , ztb_vin :: ![RawTxIn] + , ztb_vout :: ![RawTxOut] + , ztb_coinbase :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTBundle + -- | Type for the response from the `zebrad` RPC method `getinfo` data ZebraGetInfo = ZebraGetInfo { zgi_build :: !T.Text @@ -268,6 +352,32 @@ data TransparentAddress = TransparentAddress , ta_bytes :: !HexString } deriving (Eq, Prelude.Show, Read) +-- | Wrapper types for transparent elements +data RawTxIn = RawTxIn + { rti_outpoint :: !RawOutPoint + , rti_script :: !BS.ByteString + , rti_seq :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxIn + +data RawTxOut = RawTxOut + { rto_amt :: !Word64 + , rto_script :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxOut + +data RawOutPoint = RawOutPoint + { rop_hash :: !BS.ByteString + , rop_n :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawOutPoint + -- * Sapling -- | A spending key for Sapling newtype SaplingSpendingKey = diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index bddb18e..b6598da 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -22,15 +22,15 @@ import C.Zcash , rustWrapperBech32Encode , rustWrapperF4Jumble , rustWrapperF4UnJumble + , rustWrapperTxRead ) import Control.Exception (SomeException(..), try) import Control.Monad.IO.Class import Data.Aeson import Data.Binary.Get import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy as LBS -import Data.HexString +import Data.HexString (HexString(..)) import qualified Data.Text as T import qualified Data.Text.Encoding as E import Foreign.Rust.Marshall.Variable @@ -38,13 +38,7 @@ import Network.HTTP.Client (HttpException(..)) import Network.HTTP.Simple import ZcashHaskell.Types -import Foreign.C.Types -import Foreign.Marshal.Array (allocaArray, peekArray) -import Foreign.Ptr (Ptr) - -import Data.Word - --- | +-- * Utility functions -- | Decode the given bytestring using Bech32 decodeBech32 :: BS.ByteString -> RawData decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode @@ -61,6 +55,7 @@ f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble f4UnJumble :: BS.ByteString -> BS.ByteString f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble +-- * Node interaction -- | Make a Zcash RPC call makeZcashCall :: (MonadIO m, FromJSON a) @@ -112,3 +107,11 @@ getBlockTime hex_block = fromIntegral $ runGet getInt32le $ LBS.fromStrict $ BS.take 4 (BS.drop 100 $ hexBytes hex_block) + +readZebraTransaction :: HexString -> Maybe RawZebraTx +readZebraTransaction hex = + if BS.length (hexBytes $ zt_id rawTx) < 1 + then Nothing + else Just rawTx + where + rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex diff --git a/test/Spec.hs b/test/Spec.hs index b173c7a..1c07cca 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -34,6 +34,7 @@ import qualified Data.Text.Encoding as E import qualified Data.Text.Lazy.Encoding as LE import qualified Data.Text.Lazy.IO as LTIO import GHC.Float.RealFracMethods (properFractionDoubleInteger) +import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck @@ -59,7 +60,12 @@ import ZcashHaskell.Types , OrchardSpendingKey(..) , Phrase(..) , RawData(..) + , RawOutPoint(..) + , RawTBundle(..) + , RawTxIn(..) + , RawTxOut(..) , RawTxResponse(..) + , RawZebraTx(..) , SaplingReceiver(..) , SaplingSpendingKey(..) , Scope(..) @@ -71,7 +77,9 @@ import ZcashHaskell.Types , UnifiedAddress(..) , UnifiedFullViewingKey(..) , ZcashNet(..) + , ZebraTxResponse(..) , decodeHexText + , fromRawTBundle , getValue ) import ZcashHaskell.Utils @@ -770,6 +778,26 @@ main = do let blkdata = "04000000c732575a3e94b7464c84b35935c2fca26e40d37b6f3278bda3d941877b192a0048cb59da8eeaf9f622d4537ec2a27918415f444d0bb143e8091b470a18a438c117fd96323fd1a9c775ed793154de0e8a2023b551431e89b5ce6f30ef047388f0bdbb0266fe81481f2c006046a8be3bca5b8819209d053a88184bb48753171accb807c38f46d00000fd400500b6a304cbc4e6caecc9b3a29ce42d71752bf5396944e28465e29d1bd7e7f3c680db3c4b85a1488d7c4e21a70c1cf50cfe4e7f70a4f1abb638f144afb90dfa69adfc7c981fd8792df19926da695356699ab76213094431bf6491b80572c310ab8e51c27cda39c771371dbf7adc60089ff5f9614577a92ce3a1e452d5d8280e3d970322e1fc5937c53369623d5e58e57672b48c2edc7a86f7d7de62e06668097547b782a01d3794a50f131695f7de3e4bf038b12abffd29b8eba618820e143ba0be57ccaa67f64511460027efb065c6ddff501376744c6b68ca01d884025e7f939521d230b1a46016d66ad3ab2262dbd477b38bbec234a19425b939dc0f1d868035c3ef99eee635c9dfd62235ede6d62f652fec499127a152e7be4f959a9bb7fcca9b335de98d14aee1330fe37f1ba28be26902a1cddbc3f67f2f6317093d3f278c1c0156e5319a0b35360e38719911b8012a8cc34893f05dc28a22787f3e0461836aef602110e621c8b9d78e991ccc0160deb8c8921f6fd13bd00d47a170b9d0b09ffd57e37467400c138823dce7c12027eb9f919b8d5f4072b331a042f6f5ebb814c6a903110cc97161787d1eb2716c8fb98d747abf321a0b079f3b6a1a0329337b4922a0d739886a03f0d475d41005733f26c675dd9694712d6ee07772a5e8f6b5871789ac14159beced65d3b2196725baa4ce04f059a7034ff0d7ce081e94d23e0115b9d8f0d99fa5b4f9771b4feaac881ac80f178c3575cc70858e40f53f1d0406253edba75e2bb6f40eb2ef4ece3df2b8dadbd3ac168b1743a0cfb68ba81ca4c838435002587852e81e0a0c3c77e11218df892ac0c2f343967b127fbb822712ceabc459ded7f3ca3e95ed5ddfe6cdfe5657da1b12ee47eb63af7a839cdbc1f52f51e349dbf2bd7d8529f814108723d97fcfda7563c4fbe3a24118352e4706f9f91406f11acde3a570e10a258fa6a5415f57a26988249a87e57f192d0408b6f56c2ef65d5e37597d144e8fe1014a4f552dc8c213b0a70b2ccff6747f6f1e0d598715d8cb0da37e0331c01da1f9e0505e9d1907628427d5d032a29fa0e14b75289b10c7d3109b590aec2a4debda9eabcbd3163f6df67efd92687d0c492a71d48b2995a658b34e96814e5674a62e3738efe53e0daf4bfe9d1031e45a246bd3b32c6d581b71c0b60ad711570bca3848c38ba5aed74d977a185d199db721c2677d4868e5afaa6cc2c7369898f58f93738681118d529194d41f0cb6103915d2e99422db387fb90046eae764a79dfe8733a7f68439d0f37e773d16ac7f0ba3a8ce0fd6746d038704b2a0a979659a859682581fee0be3fc50eed13e85e6b52f398d16b59f5f738c1eb289b3861d636b961d9220c1f76bf8a8be3ffd1e2472461202285aa1c5b7eac58564aaab0beedb65e1078517e031888aa3cb8843876aaf19311d5150529a10c9d9bce1538f7533aff126e518ef1ebd919f26aa3869ed1ced167cb1de6075897ad2dc461b9156922c4ee387f8e1635e95564d3964e47d39e6e82079e0220df00614ecd4fd31a5f3d13c9191fc65037ad3cd651f055cc795260f21bb0ba8acd7e3b65b5ff81d5a1b187a777c826f96637e00f8147c4bd43753fe9e0cd73564b53a0e9eab27745505ccd2e4a1a217307c66030ca115580ce6ecb9a8b043ca41bd2de15abd13838d130d3cfecd8d0e3bb6c982bf1f9cdc799c3256e2d722d3f13cd7acdb9d75d7e36483b2ae01e5dcb70db824eadd9c3955d79557d612a879fa6da1da479807640e37bf25a22091f1cf6fb5293e60e5616416fb2adb1d9892258e8ac6859978159338d3b6e2063b9d842a24d61d0616506d5088fe76025df35619febd57f92e30699a0df8731bdbad0775922bb12fb1697d9da833010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603204c2a010cffffffff0480b2e60e000000001976a914278aff0c0f8734638ce81aaef4ab0afddd36552888ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000204c2a000000000000000000000000" getBlockTime (hexString blkdata) `shouldBe` 1711455165 + it "Raw transaction from faucet" $ do + let h = + hexString + "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02" + let t = readZebraTransaction h + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let tb = zt_tBundle t' + fromRawTBundle tb `shouldNotBe` Nothing + it "Raw transaction" $ do + let h = + hexString + "0400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603204c2a010cffffffff0480b2e60e000000001976a914278aff0c0f8734638ce81aaef4ab0afddd36552888ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000204c2a000000000000000000000000" + let t = readZebraTransaction h + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let tb = zt_tBundle t' + fromRawTBundle tb `shouldNotBe` Nothing -- | Properties prop_PhraseLength :: Property diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 4fecf64..534cc39 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: zcash-haskell -version: 0.5.1.0 +version: 0.5.2.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain @@ -59,6 +59,7 @@ library , text , haskoin-core , secp256k1-haskell + , utf8-string build-tool-depends: c2hs:c2hs default-language: Haskell2010 @@ -76,6 +77,7 @@ test-suite zcash-haskell-test , haskoin-core , hexstring >= 0.12.1 , hspec + , HUnit , QuickCheck , quickcheck-transformer , text diff --git a/zebrablock.json b/zebrablock.json new file mode 100644 index 0000000..5665f92 --- /dev/null +++ b/zebrablock.json @@ -0,0 +1,20 @@ +{ + "result": { + "hash": "0041ee9cb0e256a73c92bb72d830143c402ea350152f56f19f74d23cf51418fb", + "confirmations": 3583, + "height": 2767099, + "tx": [ + "d169ec3eda57dc750edfc1aa6b8ffb4ed2065780bfd5964de34b529503ec372f", + "987fcdb9bd37cbb5b205a8336de60d043f7028bebaa372828d81f3da296c7ef9" + ], + "trees": { + "sapling": { + "size": 129349 + }, + "orchard": { + "size": 39382 + } + } + }, + "id": 123 +} diff --git a/zebrahexblock.json b/zebrahexblock.json new file mode 100644 index 0000000..19d6b90 --- /dev/null +++ b/zebrahexblock.json @@ -0,0 +1,4 @@ +{ + "result": "040000006deacb36066fbbf99f00436c5a384e885c0cecff393b922ccfb22ed81b9e3600722039ae03cfdbee344a18774168795a936a720db144408a9abd92c55eb4a276f1b96e93c15bc2943a1f2483e466c456d2ea22ad82eea3d5788dbb5448bcb30c33d0fd658002451f6c005c8fa5b5d0cb7a24d326de3fd34422e22718a0c5e23972800292ed830000fd4005022b23ffe50b150fe854961e44e5d752975857fbc5026bc06a7550448e858e71dc7d927b44a04a325df027973f510de1ebb56327d7ed086b7419ff71db198940760dcede94a775fd67f515836ec4caa7d17bdc6e0ec8924c9dc74945496e673d104b85d35df91ec33942c84b7404579023b24d78b30bcde7563740f3a10a159918daafa18d955b0bb74c94782195ff5195936b21e6ec853a8ef269b21ac325675445b1815dfab18904ad6e2ad19ec8a753d1f0fd3b9171e0ea29f274bb261b028508d984fcdb9ed3df6e77ba3eb038d606d517b7461ebee25b57de47b2f2dde11c7504bacd271f2f44e405f79faaf99962145aa537b2b1f5f4f55ea30657f48ae51f0aff8f1bc1669311607cf941717e561b5e7e44bd48adc9aafba49d947deabe07e475c52e3eb13d485e60790db1a04540b4dfe992578b9758ed42ad8bee87a60b71cc88357f2d654a368cad751455057ed219766206094c899100ffc2c1b306409931d608f99a4ba850403eeaf092ee8b47b24dfad9fe18520d3ecaa98d7cf915f270629ab3fa8214f439cd0a160df22be1d99d7a979bbbd1fcb2dfd8ccfafa1207691013f2a524e1613150e0c679ac5950cb08211defa839fc537d5d941c6141e9d724ff468b9759ec7eb825132a2a78f5b9c413ee9674b44dd25f8bb65d1e839a24b36ee0eca01129935e73437e4a08edc8a2ff97d10752867f0e46cbc0ba2ae7459a7f9fbf41da3ea7be2618caa57728aae1d378f2a408f9569da556d0fa630c183d94dd542052dbc9b52ef75585ca16cf5793240f61b15dca17bead2cc524856b36c12a74c7187480090e8c01f6c79c3cafe4e50eac4c89f6991df7ff040913e3f70058aaa123f015a86b433b8a621b555f4d1abacf785c477257cc3f43ba13220e29ef653ab6be34ce223455e9bb5d775a19200267035efe861ce55503545cc85db40f59cbb4f049154983509e19877d181c3ffeff7c8fc24f1ea3f22e2513de60f340a8c7d019b76175055bc87f94dfb6381a5319e237ab5d269e1fdbe7a9e38d7fcadbf32528e1f295f5fafd1bde5b09a24c0d04d2a401e8efd352ac51b24da627ed62c85c5fefdd1019b0f9ffd86a2c30ed03d2b1b3181e261f7b05accb95a8b1109b72dd1759b7158a1b7a2c913b0fd6b5ad1dba25740139070648641739985e9e82036e9941c15580832dc4258f425c44296cc7b0938910b29cbdfcdc48df4b753744297fc656397ff504c50ea77f0cf229eb25849621e417275bb58f0b773a173d69bdf1339438b5cd5a44871436b8f56e37b059ae06ae61517e2e2857d5b422846f15d440d46fe8be4217836c545d56cfa75d46539cd0b71311e40abe30c21766ca99310bb5bab07061d69b250fc99f855c7b1218275dbe69d2d1b0aba51815126e08f68553d99de9078aad6a732b16618578127604aa0c565b2a5f0f1f15f9916ee72ffb43ace3a615bf74c461ac150fc5fa3537c78537d3aec997a1b426df2db27ad4b2dad1d34cabe61c16a9b9118ec017b650c4fc1669ecb725330c1849136e503d719b081157704377b8c778cf79031e1d25d98f4d3eb537aa564191404132231db19a64102a4c37395a86c6d557715379e39b6ec3bd7e5e4611ceeb45198e577f3efd92dea57f694ccbb20b0b44082a22cb61c758c063c862156ba6093aeed8986e1f0afd3a26f46022d515af226bdac94fb0f61f57708c2285c39c8df04a65cbdbb3fa06a53482131ed1bd8a66ed9fcdb7a995a37de76a67d75932deb989fdf565084b10954caca8919dc125483d722a02207838ccb94bb9df367e23ae37efe8c55d23bdf9cdb31d5defbc0fe25fd6dd1bf53bf30112c3babb9161d42cd5a58330cc4a44c999b2bfc701a45e7ec3b5ef493c9e1887020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603fb382a011cffffffff0468b6e60e000000001976a91401cb2de6a35b807a6e75d7fbfcc8107b2b9d39f488ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000fb382a0000000000000000000000000400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "id": 123 +} diff --git a/zebratx.json b/zebratx.json new file mode 100644 index 0000000..83f6b4b --- /dev/null +++ b/zebratx.json @@ -0,0 +1,8 @@ +{ + "result": { + "hex": "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "height": 2767099, + "confirmations": 3582 + }, + "id": 123 +}