{-# LANGUAGE FlexibleContexts #-}

-- |
-- Module      :  Network.Polkadot.Extrinsic
-- Copyright   :  Aleksandr Krupenkin 2016-2024
-- License     :  Apache-2.0
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  unportable
--
-- Extrinsic is a piece of data from external world.
--

module Network.Polkadot.Extrinsic
  ( Extrinsic
  , SignedExtra
  , sign_and_send
  , mortal_max
  , new_extra'
  , new_extra
  ) where

import           Codec.Scale                                                   (Compact (..),
                                                                                Encode,
                                                                                encode)
import           Data.ByteArray.HexString                                      (HexString)
import           Data.Maybe                                                    (fromJust)
import           Data.Text.Encoding                                            (decodeUtf8)
import           Network.JsonRpc.TinyClient                                    (JsonRpc)

import           Network.Polkadot.Account                                      (AccountId,
                                                                                IdentifyAccount (..),
                                                                                Ss58Codec (to_ss58check))
import           Network.Polkadot.Crypto                                       (MultiPair (..))
import           Network.Polkadot.Extrinsic.Era                                (Era (..))
import           Network.Polkadot.Extrinsic.SignedExtension.System
import           Network.Polkadot.Extrinsic.SignedExtension.TransactionPayment
import           Network.Polkadot.Extrinsic.Unchecked                          (UncheckedExtrinsic,
                                                                                sign_extrinsic)
import           Network.Polkadot.Primitives                                   (Balance,
                                                                                Index)
import qualified Network.Polkadot.Primitives                                   as P (MultiAddress,
                                                                                     MultiSignature)
import           Network.Polkadot.Rpc.Account                                  (nextIndex)
import           Network.Polkadot.Rpc.Author                                   (submitExtrinsic)
import           Network.Polkadot.Rpc.Chain                                    (getHeader)
import           Network.Polkadot.Rpc.Types                                    (headerNumber,
                                                                                unBlockNumber)

-- | Default Polkadot compatible extrinsic type.
type Extrinsic a = UncheckedExtrinsic a P.MultiAddress P.MultiSignature SignedExtra

-- | Default Polkadot signed extra.
type SignedExtra =
  ( CheckSpecVersion
  , CheckTxVersion
  , CheckGenesis
  , CheckEra
  , CheckNonce
  , CheckWeight
  , ChargeTransactionPayment
  )

new_extra :: (Ss58Codec a, JsonRpc m)
          => a
          -- ^ Transaction sender address.
          -> Balance
          -- ^ Transaction tips, or set zero for no tips.
          -> m SignedExtra
          -- ^ Returns Polkadot transaction extra.
new_extra :: forall a (m :: * -> *).
(Ss58Codec a, JsonRpc m) =>
a -> Balance -> m SignedExtra
new_extra a
account_id Balance
tip = do
    Index
nonce <- Int -> Index
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Index) -> m Int -> m Index
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> m Int
forall (m :: * -> *). JsonRpc m => Text -> m Int
nextIndex Text
ss58account
    Era
era <- m Era
forall (m :: * -> *). JsonRpc m => m Era
mortal_max
    SignedExtra -> m SignedExtra
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (SignedExtra -> m SignedExtra) -> SignedExtra -> m SignedExtra
forall a b. (a -> b) -> a -> b
$ Era -> Index -> Balance -> SignedExtra
new_extra' Era
era Index
nonce Balance
tip
  where
    ss58account :: Text
ss58account = ByteString -> Text
decodeUtf8 (ByteString -> Text) -> ByteString -> Text
forall a b. (a -> b) -> a -> b
$ a -> ByteString
forall a. Ss58Codec a => a -> ByteString
to_ss58check a
account_id

-- | Create signed extra from general data.
new_extra' :: Era
           -- ^ Transaction mortality.
           -> Index
           -- ^ Transaction nonce value.
           -> Balance
           -- ^ Transaction tips, or set zero for no tips.
           -> SignedExtra
           -- ^ Returns Polkadot transaction extra.
new_extra' :: Era -> Index -> Balance -> SignedExtra
new_extra' Era
era Index
nonce Balance
tip =
    ( CheckSpecVersion
CheckSpecVersion
    , CheckTxVersion
CheckTxVersion
    , CheckGenesis
CheckGenesis
    , Era -> CheckEra
CheckEra Era
era
    , Compact Index -> CheckNonce
CheckNonce (Index -> Compact Index
forall a. a -> Compact a
Compact Index
nonce)
    , CheckWeight
CheckWeight
    , Compact Balance -> ChargeTransactionPayment
ChargeTransactionPayment (Balance -> Compact Balance
forall a. a -> Compact a
Compact Balance
tip)
    )

-- | Create a mortal 'Era' with biggest lifetime period.
--
-- Note: The assumption is runtime has `BlockHashCount` = 2400. This is common
-- for Polkadot runtimes.
mortal_max :: JsonRpc m => m Era
mortal_max :: forall (m :: * -> *). JsonRpc m => m Era
mortal_max = do
    Integer
current <- (BlockNumber -> Integer
unBlockNumber (BlockNumber -> Integer)
-> (Maybe Header -> BlockNumber) -> Maybe Header -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> BlockNumber
headerNumber (Header -> BlockNumber)
-> (Maybe Header -> Header) -> Maybe Header -> BlockNumber
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Header -> Header
forall a. HasCallStack => Maybe a -> a
fromJust) (Maybe Header -> Integer) -> m (Maybe Header) -> m Integer
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe HexString -> m (Maybe Header)
forall (m :: * -> *).
JsonRpc m =>
Maybe HexString -> m (Maybe Header)
getHeader Maybe HexString
forall a. Maybe a
Nothing
    Era -> m Era
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Era -> m Era) -> Era -> m Era
forall a b. (a -> b) -> a -> b
$ Index -> Index -> Era
MortalEra Index
2048 (Index -> Era) -> Index -> Era
forall a b. (a -> b) -> a -> b
$ Integer -> Index
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Integer
current Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1)

-- | Sign extrinsic and send it using node RPC call.
sign_and_send :: ( MultiPair pair
                 , IdentifyAccount (MultiSigner pair)
                 , Ss58Codec (AccountId (MultiSigner pair))
                 , Encode (MultiAddress pair)
                 , Encode (MultiSignature pair)
                 , Encode call
                 , JsonRpc m
                 )
              => pair
              -- ^ Sender account pair.
              -> call
              -- ^ Runtime function to call.
              -> Balance
              -- ^ Tips for speedup transaction (set zero for no boost).
              -> m HexString
              -- ^ Transaction hash.
sign_and_send :: forall pair call (m :: * -> *).
(MultiPair pair, IdentifyAccount (MultiSigner pair),
 Ss58Codec (AccountId (MultiSigner pair)),
 Encode (MultiAddress pair), Encode (MultiSignature pair),
 Encode call, JsonRpc m) =>
pair -> call -> Balance -> m HexString
sign_and_send pair
pair call
call Balance
tip = do
    SignedExtra
extra <- AccountId (MultiSigner pair) -> Balance -> m SignedExtra
forall a (m :: * -> *).
(Ss58Codec a, JsonRpc m) =>
a -> Balance -> m SignedExtra
new_extra AccountId (MultiSigner pair)
account_id Balance
tip
    UncheckedExtrinsic
  call (MultiAddress pair) (MultiSignature pair) SignedExtra
xt <- pair
-> call
-> SignedExtra
-> m (UncheckedExtrinsic
        call (MultiAddress pair) (MultiSignature pair) SignedExtra)
forall a c e (m :: * -> *).
(MultiPair a, Encode c, SignedExtension e, JsonRpc m) =>
a
-> c
-> e
-> m (UncheckedExtrinsic c (MultiAddress a) (MultiSignature a) e)
sign_extrinsic pair
pair call
call SignedExtra
extra
    HexString -> m HexString
forall (m :: * -> *). JsonRpc m => HexString -> m HexString
submitExtrinsic (UncheckedExtrinsic
  call (MultiAddress pair) (MultiSignature pair) SignedExtra
-> HexString
forall a ba. (Encode a, ByteArray ba) => a -> ba
encode UncheckedExtrinsic
  call (MultiAddress pair) (MultiSignature pair) SignedExtra
xt)
  where
    account_id :: AccountId (MultiSigner pair)
account_id = MultiSigner pair -> AccountId (MultiSigner pair)
forall a. IdentifyAccount a => a -> AccountId a
into_account (pair -> MultiSigner pair
forall a. MultiPair a => a -> MultiSigner a
multi_signer pair
pair)