solana-haskell-sdk-1.2.0.0: src/Network/Solana/Core/Account.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Network.Solana.Core.Account
-- Description : Accounts, account state, and the 'Lamport' unit.
module Network.Solana.Core.Account where
import Data.Aeson.Types
import Data.ByteString qualified as S
import Data.Text qualified as T
import Data.Vector qualified as V
import Data.Word (Word64)
import GHC.Base (Alternative (..))
import GHC.Generics (Generic)
import Network.Solana.Constants
import Network.Solana.Core.Crypto
import Text.Printf (printf)
-- | Lamport is the smallest unit of SOL (1 SOL = 1 billion lamports).
-- 'Show' renders the amount converted to SOL (e.g. @0.000000001 SOL ◎@), not the raw count.
newtype Lamport = Lamport
{ -- | The raw lamport amount.
unLamport :: Word64
}
deriving (Eq, Ord, Generic, Enum)
deriving newtype (Num, Real, Integral)
instance ToJSON Lamport where
toJSON :: Lamport -> Value
toJSON (Lamport amnt) = toJSON amnt
instance FromJSON Lamport where
parseJSON :: Value -> Parser Lamport
parseJSON value = do
v <- parseJSON @Word64 value
return $ Lamport v
instance Show Lamport where
show :: Lamport -> String
show (Lamport n) =
let (sol :: Double) = fromIntegral n / fromIntegral lamportsPerSol
in printf "%.9f SOL ◎" sol
------------------------------------------------------------------------------------------------
-- ** Account
------------------------------------------------------------------------------------------------
-- | An account paired with its address, as returned by RPC methods such as
-- @getProgramAccounts@.
data Account = Account
{ -- | The account's address.
pubkey :: SolanaPublicKey,
-- | The account's state.
account :: AccountInfo
}
deriving (Show, Eq, Generic, ToJSON, FromJSON)
------------------------------------------------------------------------------------------------
-- ** Account Info
------------------------------------------------------------------------------------------------
-- | The base state shared by every Solana account. Accounts hold at most
-- 10 MiB of data.
data AccountInfo = AccountInfo
{ -- | Amount of lamports in the account
lamports :: Lamport,
-- | The account's data: executable code for program accounts,
-- program-defined state otherwise. Named @dataField@ because @data@ is
-- reserved in Haskell; serialized as @data@ in JSON.
dataField :: AccountData,
-- | The program that owns this account. Only the owner program may
-- modify the account's data or debit its lamports.
owner :: SolanaPublicKey,
-- | A boolean flag that indicates whether this account contains a loaded program.
executable :: Bool
}
deriving (Show, Eq, Generic, ToJSON)
instance FromJSON AccountInfo where
parseJSON :: Value -> Parser AccountInfo
parseJSON = do
withObject "AccountInfo" $
\v ->
AccountInfo
<$> v .: "lamports"
<*> v .: "data"
<*> v .: "owner"
<*> v .: "executable"
------------------------------------------------------------------------------------------------
-- ** Account Data
------------------------------------------------------------------------------------------------
-- | A byte array that stores arbitrary data for an account.
data AccountData
= AccountDataBinary
{ accData :: S.ByteString
}
| AccountDataJSON {accDataObj :: String}
deriving (Eq, Generic)
instance Show AccountData where
show :: AccountData -> String
show (AccountDataBinary bs) = toBase58String bs
show (AccountDataJSON o) = show o
instance ToJSON AccountData where
toJSON :: AccountData -> Value
toJSON ac = toJSON (show ac)
instance FromJSON AccountData where
parseJSON :: Value -> Parser AccountData
parseJSON v =
let base64StringParser =
withText
"AccountDataText"
(return . AccountDataBinary . fromBase64String . T.unpack)
base58StringParser =
withText
"AccountDataText"
( maybe (fail "AccountData: invalid base58") (pure . AccountDataBinary)
. fromBase58String
. T.unpack
)
encodedParser =
withArray
"AccountDataArray"
( \arr ->
if V.length arr /= 2
then fail "AccountData: expected [data, encoding] pair"
else case arr V.! 1 of
"base58" -> base58StringParser $ arr V.! 0
"base64" -> base64StringParser $ arr V.! 0
"json" -> withText "AccountDataJSON" (return . AccountDataJSON . T.unpack) $ arr V.! 0
"jsonParsed" -> withText "AccountDataJSON" (return . AccountDataJSON . T.unpack) $ arr V.! 0
other -> fail ("AccountData: unsupported encoding: " <> show other)
)
v
in base64StringParser v <|> encodedParser