Compare commits

...

22 commits

Author SHA1 Message Date
93a04c09d3
Merge branch 'dev040' into rvv040 2024-03-31 06:43:17 -05:00
4b064af4de
Merge pull request 'Implements parsing of transparent bundles' (#47) from rav001 into dev040
Reviewed-on: #47
2024-03-27 18:25:47 +00:00
52950885c1
Implements parsing of transparent bundles 2024-03-27 13:22:34 -05:00
899b79f155
Merge pull request 'Correct the parsing of OutPoint' (#46) from rav001 into dev040
Reviewed-on: #46
2024-03-27 14:05:17 +00:00
0fb02b2514
Correct parsing of OutPoint 2024-03-27 09:00:00 -05:00
1674f9b592
Merge branch 'dev040' into rav001 2024-03-26 15:40:11 -05:00
bb9d336dc3
Implements low-level transparent components (#45)
This PR includes components for the deserialization of low-level part of transparent components.

Reviewed-on: #45
Co-authored-by: Rene Vergara <rene@vergara.network>
Co-committed-by: Rene Vergara <rene@vergara.network>
2024-03-26 20:39:31 +00:00
f593fefd7f
Add types for low-level transparent components 2024-03-26 15:37:04 -05:00
ee27860299
Merge branch 'dev040' into rav001 2024-03-26 09:56:38 -05:00
4e86a2f5a4
Zebra Raw Tx deserialization (#44)
Implements features to deserialize the hex representation of of a Zebra Transaction

Reviewed-on: #44
Co-authored-by: Rene Vergara <rene@vergara.network>
Co-committed-by: Rene Vergara <rene@vergara.network>
2024-03-26 14:56:10 +00:00
921d34383a
Merge remote-tracking branch 'forge/rav001' into rav001 2024-03-26 09:53:17 -05:00
b0df0480c5
Implement Transaction hex deserialization 2024-03-26 09:50:49 -05:00
03d53aa303
Merge branch 'dev040' into rav001 2024-03-22 13:04:35 -05:00
07fa36ed7d
Merge branch 'dev040' into rav001 2024-03-22 18:03:45 +00:00
5b6ce3f29b
Account for missing block time field 2024-03-21 19:27:09 -05:00
d659368cff
Merge branch 'dev040' into rav001 2024-03-21 15:14:13 -05:00
9c8a851ead
Implement Sapling spends 2024-03-21 15:12:22 -05:00
69bce58345
Improve exception handling of Zebra calls 2024-03-21 12:52:45 -05:00
adc7150b81
Merge branch 'dev040' into rav001 2024-03-20 14:34:11 -05:00
517b736c9a
Allow for missing result in RPC response 2024-03-20 14:16:12 -05:00
d1eaf1de4d
Merge branch 'dev040' into rav001 2024-03-20 11:29:31 -05:00
e7050f03c0
Upgrade Zebra call 2024-03-20 11:13:02 -05:00
10 changed files with 354 additions and 29 deletions

View file

@ -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/), 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). 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] ## [0.5.1.0]
### Added ### Added

View file

@ -31,9 +31,18 @@ use zcash_primitives::{
sapling::DiversifierKey sapling::DiversifierKey
}, },
zip339::{Count, Mnemonic}, zip339::{Count, Mnemonic},
transaction::components::sapling::{ transaction::components::{
GrothProofBytes, transparent::{
OutputDescription Bundle as TransparentBundle,
TxIn,
TxOut,
OutPoint,
Authorized
},
sapling::{
GrothProofBytes,
OutputDescription
}
}, },
sapling::{ sapling::{
PaymentAddress, PaymentAddress,
@ -221,6 +230,101 @@ impl Hua {
} }
} }
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Htx {
txid: Vec<u8>,
locktime: u32,
expiry: u32,
t_bundle: HTBundle
}
impl<RW> ToHaskell<RW> for Htx {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct HTBundle {
empty: bool,
vin: Vec<HTxIn>,
vout: Vec<HTxOut>,
coinbase: bool
}
impl<RW> ToHaskell<RW> for HTBundle {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
impl HTBundle {
pub fn from_bundle(b: &TransparentBundle<Authorized>) -> 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<u8>,
sequence: u32
}
impl<RW> ToHaskell<RW> for HTxIn {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
impl HTxIn {
pub fn pack(t: &TxIn<Authorized>) -> 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<u8>
}
impl<RW> ToHaskell<RW> for HTxOut {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> 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<u8>,
index: u32
}
impl<RW> ToHaskell<RW> for Houtpoint {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> 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)] #[derive(BorshSerialize, BorshDeserialize)]
pub struct Hufvk { pub struct Hufvk {
net: u8, 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<u8> = 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] #[no_mangle]
pub extern "C" fn rust_wrapper_tx_parse( pub extern "C" fn rust_wrapper_tx_parse(
tx: *const u8, tx: *const u8,

View file

@ -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 {# fun unsafe rust_wrapper_gen_seed_phrase as rustWrapperGenSeedPhrase
{ getVarBuffer `Buffer Phrase'& } -> `()' { getVarBuffer `Buffer Phrase'& } -> `()'
#} #}

View file

@ -16,6 +16,7 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
@ -29,9 +30,11 @@ import Data.Aeson
import qualified Data.ByteArray as BA import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C 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.HexString
import Data.Int import Data.Int
import Data.Maybe (fromMaybe) import Data.Maybe (fromJust, fromMaybe)
import Data.Structured import Data.Structured
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
@ -39,7 +42,9 @@ import Data.Word
import qualified GHC.Generics as GHC import qualified GHC.Generics as GHC
import qualified Generics.SOP as SOP import qualified Generics.SOP as SOP
import Haskoin.Address (Address) import Haskoin.Address (Address)
import qualified Haskoin.Crypto.Hash as H (Hash256(..))
import Haskoin.Crypto.Keys.Extended (XPrvKey) import Haskoin.Crypto.Keys.Extended (XPrvKey)
import qualified Haskoin.Transaction.Common as H
-- * General -- * General
-- --
@ -89,6 +94,19 @@ data ZcashNet
type AccountId = Int 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 -- ** Constants
-- | Type for coin types on the different networks -- | Type for coin types on the different networks
data CoinType data CoinType
@ -104,7 +122,50 @@ getValue c =
TestNetCoin -> 1 TestNetCoin -> 1
RegTestNetCoin -> 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 sapExtSpendingKeyHrp = "secret-extended-key-main" :: String
sapExtFullViewingKeyHrp = "zxviews" :: String sapExtFullViewingKeyHrp = "zxviews" :: String
@ -117,7 +178,7 @@ sapTestExtFullViewingKeyHrp = "zxviewtestsapling" :: String
sapTestPaymentAddressHrp = "ztestsapling" :: String sapTestPaymentAddressHrp = "ztestsapling" :: String
-- | Constants for Unified Human-readable part -- *** Constants for Unified Human-readable part
uniPaymentAddressHrp = "u" :: T.Text uniPaymentAddressHrp = "u" :: T.Text
uniFullViewingKeyHrp = "uview" :: T.Text uniFullViewingKeyHrp = "uview" :: T.Text
@ -130,19 +191,6 @@ uniTestFullViewingKeyHrp = "uviewtest" :: T.Text
uniTestIncomingViewingKeyHrp = "uivktest" :: 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 -- * RPC
-- | A type to model Zcash RPC calls -- | A type to model Zcash RPC calls
data RpcCall = RpcCall data RpcCall = RpcCall
@ -217,6 +265,42 @@ data RawTxResponse = RawTxResponse
} deriving (Prelude.Show, Eq, Read) } deriving (Prelude.Show, Eq, Read)
-- ** `zebrad` -- ** `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` -- | Type for the response from the `zebrad` RPC method `getinfo`
data ZebraGetInfo = ZebraGetInfo data ZebraGetInfo = ZebraGetInfo
{ zgi_build :: !T.Text { zgi_build :: !T.Text
@ -268,6 +352,32 @@ data TransparentAddress = TransparentAddress
, ta_bytes :: !HexString , ta_bytes :: !HexString
} deriving (Eq, Prelude.Show, Read) } 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 -- * Sapling
-- | A spending key for Sapling -- | A spending key for Sapling
newtype SaplingSpendingKey = newtype SaplingSpendingKey =

View file

@ -22,15 +22,15 @@ import C.Zcash
, rustWrapperBech32Encode , rustWrapperBech32Encode
, rustWrapperF4Jumble , rustWrapperF4Jumble
, rustWrapperF4UnJumble , rustWrapperF4UnJumble
, rustWrapperTxRead
) )
import Control.Exception (SomeException(..), try) import Control.Exception (SomeException(..), try)
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Aeson import Data.Aeson
import Data.Binary.Get import Data.Binary.Get
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import Data.HexString import Data.HexString (HexString(..))
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
@ -38,13 +38,7 @@ import Network.HTTP.Client (HttpException(..))
import Network.HTTP.Simple import Network.HTTP.Simple
import ZcashHaskell.Types import ZcashHaskell.Types
import Foreign.C.Types -- * Utility functions
import Foreign.Marshal.Array (allocaArray, peekArray)
import Foreign.Ptr (Ptr)
import Data.Word
-- |
-- | Decode the given bytestring using Bech32 -- | Decode the given bytestring using Bech32
decodeBech32 :: BS.ByteString -> RawData decodeBech32 :: BS.ByteString -> RawData
decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode
@ -61,6 +55,7 @@ f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble
f4UnJumble :: BS.ByteString -> BS.ByteString f4UnJumble :: BS.ByteString -> BS.ByteString
f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble
-- * Node interaction
-- | Make a Zcash RPC call -- | Make a Zcash RPC call
makeZcashCall :: makeZcashCall ::
(MonadIO m, FromJSON a) (MonadIO m, FromJSON a)
@ -112,3 +107,11 @@ getBlockTime hex_block =
fromIntegral $ fromIntegral $
runGet getInt32le $ runGet getInt32le $
LBS.fromStrict $ BS.take 4 (BS.drop 100 $ hexBytes hex_block) 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

View file

@ -34,6 +34,7 @@ import qualified Data.Text.Encoding as E
import qualified Data.Text.Lazy.Encoding as LE import qualified Data.Text.Lazy.Encoding as LE
import qualified Data.Text.Lazy.IO as LTIO import qualified Data.Text.Lazy.IO as LTIO
import GHC.Float.RealFracMethods (properFractionDoubleInteger) import GHC.Float.RealFracMethods (properFractionDoubleInteger)
import Test.HUnit
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
import Test.QuickCheck import Test.QuickCheck
@ -59,7 +60,12 @@ import ZcashHaskell.Types
, OrchardSpendingKey(..) , OrchardSpendingKey(..)
, Phrase(..) , Phrase(..)
, RawData(..) , RawData(..)
, RawOutPoint(..)
, RawTBundle(..)
, RawTxIn(..)
, RawTxOut(..)
, RawTxResponse(..) , RawTxResponse(..)
, RawZebraTx(..)
, SaplingReceiver(..) , SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, Scope(..) , Scope(..)
@ -71,7 +77,9 @@ import ZcashHaskell.Types
, UnifiedAddress(..) , UnifiedAddress(..)
, UnifiedFullViewingKey(..) , UnifiedFullViewingKey(..)
, ZcashNet(..) , ZcashNet(..)
, ZebraTxResponse(..)
, decodeHexText , decodeHexText
, fromRawTBundle
, getValue , getValue
) )
import ZcashHaskell.Utils import ZcashHaskell.Utils
@ -770,6 +778,26 @@ main = do
let blkdata = let blkdata =
"04000000c732575a3e94b7464c84b35935c2fca26e40d37b6f3278bda3d941877b192a0048cb59da8eeaf9f622d4537ec2a27918415f444d0bb143e8091b470a18a438c117fd96323fd1a9c775ed793154de0e8a2023b551431e89b5ce6f30ef047388f0bdbb0266fe81481f2c006046a8be3bca5b8819209d053a88184bb48753171accb807c38f46d00000fd400500b6a304cbc4e6caecc9b3a29ce42d71752bf5396944e28465e29d1bd7e7f3c680db3c4b85a1488d7c4e21a70c1cf50cfe4e7f70a4f1abb638f144afb90dfa69adfc7c981fd8792df19926da695356699ab76213094431bf6491b80572c310ab8e51c27cda39c771371dbf7adc60089ff5f9614577a92ce3a1e452d5d8280e3d970322e1fc5937c53369623d5e58e57672b48c2edc7a86f7d7de62e06668097547b782a01d3794a50f131695f7de3e4bf038b12abffd29b8eba618820e143ba0be57ccaa67f64511460027efb065c6ddff501376744c6b68ca01d884025e7f939521d230b1a46016d66ad3ab2262dbd477b38bbec234a19425b939dc0f1d868035c3ef99eee635c9dfd62235ede6d62f652fec499127a152e7be4f959a9bb7fcca9b335de98d14aee1330fe37f1ba28be26902a1cddbc3f67f2f6317093d3f278c1c0156e5319a0b35360e38719911b8012a8cc34893f05dc28a22787f3e0461836aef602110e621c8b9d78e991ccc0160deb8c8921f6fd13bd00d47a170b9d0b09ffd57e37467400c138823dce7c12027eb9f919b8d5f4072b331a042f6f5ebb814c6a903110cc97161787d1eb2716c8fb98d747abf321a0b079f3b6a1a0329337b4922a0d739886a03f0d475d41005733f26c675dd9694712d6ee07772a5e8f6b5871789ac14159beced65d3b2196725baa4ce04f059a7034ff0d7ce081e94d23e0115b9d8f0d99fa5b4f9771b4feaac881ac80f178c3575cc70858e40f53f1d0406253edba75e2bb6f40eb2ef4ece3df2b8dadbd3ac168b1743a0cfb68ba81ca4c838435002587852e81e0a0c3c77e11218df892ac0c2f343967b127fbb822712ceabc459ded7f3ca3e95ed5ddfe6cdfe5657da1b12ee47eb63af7a839cdbc1f52f51e349dbf2bd7d8529f814108723d97fcfda7563c4fbe3a24118352e4706f9f91406f11acde3a570e10a258fa6a5415f57a26988249a87e57f192d0408b6f56c2ef65d5e37597d144e8fe1014a4f552dc8c213b0a70b2ccff6747f6f1e0d598715d8cb0da37e0331c01da1f9e0505e9d1907628427d5d032a29fa0e14b75289b10c7d3109b590aec2a4debda9eabcbd3163f6df67efd92687d0c492a71d48b2995a658b34e96814e5674a62e3738efe53e0daf4bfe9d1031e45a246bd3b32c6d581b71c0b60ad711570bca3848c38ba5aed74d977a185d199db721c2677d4868e5afaa6cc2c7369898f58f93738681118d529194d41f0cb6103915d2e99422db387fb90046eae764a79dfe8733a7f68439d0f37e773d16ac7f0ba3a8ce0fd6746d038704b2a0a979659a859682581fee0be3fc50eed13e85e6b52f398d16b59f5f738c1eb289b3861d636b961d9220c1f76bf8a8be3ffd1e2472461202285aa1c5b7eac58564aaab0beedb65e1078517e031888aa3cb8843876aaf19311d5150529a10c9d9bce1538f7533aff126e518ef1ebd919f26aa3869ed1ced167cb1de6075897ad2dc461b9156922c4ee387f8e1635e95564d3964e47d39e6e82079e0220df00614ecd4fd31a5f3d13c9191fc65037ad3cd651f055cc795260f21bb0ba8acd7e3b65b5ff81d5a1b187a777c826f96637e00f8147c4bd43753fe9e0cd73564b53a0e9eab27745505ccd2e4a1a217307c66030ca115580ce6ecb9a8b043ca41bd2de15abd13838d130d3cfecd8d0e3bb6c982bf1f9cdc799c3256e2d722d3f13cd7acdb9d75d7e36483b2ae01e5dcb70db824eadd9c3955d79557d612a879fa6da1da479807640e37bf25a22091f1cf6fb5293e60e5616416fb2adb1d9892258e8ac6859978159338d3b6e2063b9d842a24d61d0616506d5088fe76025df35619febd57f92e30699a0df8731bdbad0775922bb12fb1697d9da833010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603204c2a010cffffffff0480b2e60e000000001976a914278aff0c0f8734638ce81aaef4ab0afddd36552888ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000204c2a000000000000000000000000" "04000000c732575a3e94b7464c84b35935c2fca26e40d37b6f3278bda3d941877b192a0048cb59da8eeaf9f622d4537ec2a27918415f444d0bb143e8091b470a18a438c117fd96323fd1a9c775ed793154de0e8a2023b551431e89b5ce6f30ef047388f0bdbb0266fe81481f2c006046a8be3bca5b8819209d053a88184bb48753171accb807c38f46d00000fd400500b6a304cbc4e6caecc9b3a29ce42d71752bf5396944e28465e29d1bd7e7f3c680db3c4b85a1488d7c4e21a70c1cf50cfe4e7f70a4f1abb638f144afb90dfa69adfc7c981fd8792df19926da695356699ab76213094431bf6491b80572c310ab8e51c27cda39c771371dbf7adc60089ff5f9614577a92ce3a1e452d5d8280e3d970322e1fc5937c53369623d5e58e57672b48c2edc7a86f7d7de62e06668097547b782a01d3794a50f131695f7de3e4bf038b12abffd29b8eba618820e143ba0be57ccaa67f64511460027efb065c6ddff501376744c6b68ca01d884025e7f939521d230b1a46016d66ad3ab2262dbd477b38bbec234a19425b939dc0f1d868035c3ef99eee635c9dfd62235ede6d62f652fec499127a152e7be4f959a9bb7fcca9b335de98d14aee1330fe37f1ba28be26902a1cddbc3f67f2f6317093d3f278c1c0156e5319a0b35360e38719911b8012a8cc34893f05dc28a22787f3e0461836aef602110e621c8b9d78e991ccc0160deb8c8921f6fd13bd00d47a170b9d0b09ffd57e37467400c138823dce7c12027eb9f919b8d5f4072b331a042f6f5ebb814c6a903110cc97161787d1eb2716c8fb98d747abf321a0b079f3b6a1a0329337b4922a0d739886a03f0d475d41005733f26c675dd9694712d6ee07772a5e8f6b5871789ac14159beced65d3b2196725baa4ce04f059a7034ff0d7ce081e94d23e0115b9d8f0d99fa5b4f9771b4feaac881ac80f178c3575cc70858e40f53f1d0406253edba75e2bb6f40eb2ef4ece3df2b8dadbd3ac168b1743a0cfb68ba81ca4c838435002587852e81e0a0c3c77e11218df892ac0c2f343967b127fbb822712ceabc459ded7f3ca3e95ed5ddfe6cdfe5657da1b12ee47eb63af7a839cdbc1f52f51e349dbf2bd7d8529f814108723d97fcfda7563c4fbe3a24118352e4706f9f91406f11acde3a570e10a258fa6a5415f57a26988249a87e57f192d0408b6f56c2ef65d5e37597d144e8fe1014a4f552dc8c213b0a70b2ccff6747f6f1e0d598715d8cb0da37e0331c01da1f9e0505e9d1907628427d5d032a29fa0e14b75289b10c7d3109b590aec2a4debda9eabcbd3163f6df67efd92687d0c492a71d48b2995a658b34e96814e5674a62e3738efe53e0daf4bfe9d1031e45a246bd3b32c6d581b71c0b60ad711570bca3848c38ba5aed74d977a185d199db721c2677d4868e5afaa6cc2c7369898f58f93738681118d529194d41f0cb6103915d2e99422db387fb90046eae764a79dfe8733a7f68439d0f37e773d16ac7f0ba3a8ce0fd6746d038704b2a0a979659a859682581fee0be3fc50eed13e85e6b52f398d16b59f5f738c1eb289b3861d636b961d9220c1f76bf8a8be3ffd1e2472461202285aa1c5b7eac58564aaab0beedb65e1078517e031888aa3cb8843876aaf19311d5150529a10c9d9bce1538f7533aff126e518ef1ebd919f26aa3869ed1ced167cb1de6075897ad2dc461b9156922c4ee387f8e1635e95564d3964e47d39e6e82079e0220df00614ecd4fd31a5f3d13c9191fc65037ad3cd651f055cc795260f21bb0ba8acd7e3b65b5ff81d5a1b187a777c826f96637e00f8147c4bd43753fe9e0cd73564b53a0e9eab27745505ccd2e4a1a217307c66030ca115580ce6ecb9a8b043ca41bd2de15abd13838d130d3cfecd8d0e3bb6c982bf1f9cdc799c3256e2d722d3f13cd7acdb9d75d7e36483b2ae01e5dcb70db824eadd9c3955d79557d612a879fa6da1da479807640e37bf25a22091f1cf6fb5293e60e5616416fb2adb1d9892258e8ac6859978159338d3b6e2063b9d842a24d61d0616506d5088fe76025df35619febd57f92e30699a0df8731bdbad0775922bb12fb1697d9da833010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603204c2a010cffffffff0480b2e60e000000001976a914278aff0c0f8734638ce81aaef4ab0afddd36552888ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000204c2a000000000000000000000000"
getBlockTime (hexString blkdata) `shouldBe` 1711455165 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 -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.5.1.0 version: 0.5.2.0
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme> description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain category: Blockchain
@ -59,6 +59,7 @@ library
, text , text
, haskoin-core , haskoin-core
, secp256k1-haskell , secp256k1-haskell
, utf8-string
build-tool-depends: build-tool-depends:
c2hs:c2hs c2hs:c2hs
default-language: Haskell2010 default-language: Haskell2010
@ -76,6 +77,7 @@ test-suite zcash-haskell-test
, haskoin-core , haskoin-core
, hexstring >= 0.12.1 , hexstring >= 0.12.1
, hspec , hspec
, HUnit
, QuickCheck , QuickCheck
, quickcheck-transformer , quickcheck-transformer
, text , text

20
zebrablock.json Normal file
View file

@ -0,0 +1,20 @@
{
"result": {
"hash": "0041ee9cb0e256a73c92bb72d830143c402ea350152f56f19f74d23cf51418fb",
"confirmations": 3583,
"height": 2767099,
"tx": [
"d169ec3eda57dc750edfc1aa6b8ffb4ed2065780bfd5964de34b529503ec372f",
"987fcdb9bd37cbb5b205a8336de60d043f7028bebaa372828d81f3da296c7ef9"
],
"trees": {
"sapling": {
"size": 129349
},
"orchard": {
"size": 39382
}
}
},
"id": 123
}

4
zebrahexblock.json Normal file

File diff suppressed because one or more lines are too long

8
zebratx.json Normal file
View file

@ -0,0 +1,8 @@
{
"result": {
"hex": "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02",
"height": 2767099,
"confirmations": 3582
},
"id": 123
}