Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added examples of transaction creation to Haddock #698

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ test-suite cardano-api-test
Test.Cardano.Api.Envelope
Test.Cardano.Api.EpochLeadership
Test.Cardano.Api.Eras
Test.Cardano.Api.Experimental
Test.Cardano.Api.Genesis
Test.Cardano.Api.IO
Test.Cardano.Api.Json
Expand Down
216 changes: 212 additions & 4 deletions cardano-api/src/Cardano/Api/Experimental.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,214 @@
-- | This module provides an experimental library interface that is intended
-- to replace the existing api. It is subject to dramatic changes so use with caution.
-- |
-- This module provides an experimental library interface that is intended
-- to replace the existing API. It is subject to dramatic changes, so use with caution.
module Cardano.Api.Experimental
( -- * Tx related
( -- * Creating transactions using the new and the old API

-- |
-- Both the old and the new API can be used to create transactions, and
-- it is possible to transform a transaction created in one format to the other
-- since they have the same representation underneath. But we will be moving
-- towards using the new API and deprecating the old way, since the later is
-- simpler, closer to the ledger, and easier to maintain.
palas marked this conversation as resolved.
Show resolved Hide resolved
--
-- In both the new and the old API, in order to construct a transaction,
-- we need to construct a 'TxBodyContent', and we will need at least a
-- witness (for example, a 'ShelleyWitnessSigningKey'), to sign the transaction.
-- This hasn't changed.
--
-- In the following examples, we are using the following qualified modules:
--
-- @
-- import qualified Cardano.Api as Api -- the general `cardano-api` exports (including the old API)
-- import qualified Cardano.Api.Script as Script -- types related to scripts (Plutus and native)
-- import qualified Cardano.Api.Ledger as Ledger -- cardano-ledger re-exports
-- import qualified Cardano.Api.Experimental as Exp -- the experimental API
-- @
--
-- You can find a compilable version of these examples in 'Test.Cardano.Api.Experimental'.

-- ** Creating a 'TxBodyContent'

palas marked this conversation as resolved.
Show resolved Hide resolved
-- |
-- 'TxBodyContent' datatype provides lots of fields, because transactions can serve multiple
-- purposes, but the function 'defaultTxBodyContent' exported from 'Cardano.Api' already provides
-- a base 'TxBodyContent' with all fields set to their default values that we can use as a starting
-- in order not to have to set all fields manually.
palas marked this conversation as resolved.
Show resolved Hide resolved
--
-- The 'defaultTxBodyContent' takes, as the only parameter, the 'ShelleyBasedEra' witness for the era
-- we are working with. For example, if we are working with the 'ConwayEra', we can use 'shelleyBasedEra'
-- available in 'Cardano.Api', as follows:
--
-- @
-- let sbe = Api.shelleyBasedEra :: Api.ShelleyBasedEra Api.ConwayEra
-- @
palas marked this conversation as resolved.
Show resolved Hide resolved
palas marked this conversation as resolved.
Show resolved Hide resolved
--
-- We can also derive it from 'ConwayEra' from 'Cardano.Api.Experimental' by using the 'convert' function:
--
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think this is a bad UI. We should have a defaultTxBodyContent defined in an experimental module:

defaultTxBodyContent
  :: ()
  => Era era
  -> TxBodyContent BuildTx era

We want to avoid mixing the old api and the new api (but obviously allow for migration e.g via convert).

These haddocks are excellent but what we should do is have haddocks strictly describing the old api, the new api and how to migrate from the old to the new.

-- @
-- let era = Exp.ConwayEra
-- let sbe = Api.convert era
-- @
--
-- Let's see what creating a simple transaction would look like.
--
-- First, we choose a transaction output to spend (a UTxO). We specify which UTxO to spend by
-- providing the transaction id and the index of the output in that transaction that we want
-- to spend.
--
-- To specify the transaction id, we can use the 'deserialiseFromRawBytesHex' function on the
-- hexadecimal representation of the hash of the transaction. For example:
--
-- @
-- let (Right srcTxId) = Api.deserialiseFromRawBytesHex Api.AsTxId "be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd978"
-- @
--
-- Of course, in real code, the failure case should be handled properly.
--
-- To specify the transaction index, we can use the 'TxIx' constructor. For example:
--
-- @
-- let srcTxIx = Api.TxIx 0
-- @
--
-- Now we combine both to create a 'TxIn' value and we pair it with a witness requirement using 'BuildTxWith' :
--
-- @
-- let txIn = ( Api.TxIn srcTxId srcTxIx
-- , Api.BuildTxWith (Api.KeyWitness Api.KeyWitnessForSpending)
-- )
-- @
--
-- Next, let's specify the address of the recipient of the transaction. If we have the Bech32 representation,
-- we can use the 'deserialiseAddress' function to get the 'AddressInEra' type. For example:
--
-- @
-- let (Just destAddress) = Api.deserialiseAddress (Api.AsAddressInEra eraAsType) "addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v"
-- @
--
-- Now we can create a 'TxOut' value. For simplicity, we will assume the output is a simple payment output
-- of 10 ADA, with no datum or reference script attached to it:
--
-- @
-- let txOut = Api.TxOut
-- destAddress
-- (Api.TxOutValueShelleyBased sbe (Api.inject (Ledger.Coin 10_000_000)))
-- Api.TxOutDatumNone
palas marked this conversation as resolved.
Show resolved Hide resolved
-- Script.ReferenceScriptNone
-- @
--
-- We must also set the fee for the transaction. For example, let's set it to 2 ADA:
--
-- @
-- let txFee = Api.TxFeeExplicit sbe (Ledger.Coin 2_000_000)
-- @
--
-- Finally, we can create the 'TxBodyContent' by using the 'defaultTxBodyContent' function and
-- putting everything together:
--
-- @
-- let txBodyContent = Api.defaultTxBodyContent sbe
-- & Api.setTxIns [txIn]
-- & Api.setTxOuts [txOut]
-- & Api.setTxFee txFee
-- @
--
-- The 'txBodyContent' can now be used to create a transaction either using the old or the new API.

-- ** Creating a 'ShelleyWitnessSigningKey'

-- |
-- To sign the transaction, we need a witness. For example, a 'ShelleyWitnessSigningKey'.
--
-- There are several ways of doing this, and several ways of representing a signing key. But let us assume
-- we have the bech32 representation of the signing key. In that case we can use the 'deserialiseFromBech32' function
-- as follows:
--
-- @
-- let (Right signingKey) = Api.deserialiseFromBech32 (Api.AsSigningKey Api.AsPaymentKey) "addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms"
-- @
--
-- Then we simply wrap the signing key in a 'ShelleyWitnessSigningKey' constructor:
--
palas marked this conversation as resolved.
Show resolved Hide resolved
-- @
-- let witness = Api.WitnessPaymentKey signingKey
-- @
--
-- We could do it analogously if we wanted to use an extended key, for example, using 'AsPaymentExtendedKey' and 'WitnessPaymentExtendedKey'.

-- ** Creating a transaction using the old API

-- |
-- Now that we have a 'TxBodyContent' and a 'ShelleyWitnessSigningKey', we can create a transaction using the old API
-- easily. First, we create a transaction body using the 'createTransactionBody' function and the 'ShelleyBasedEra' witness
-- that we defined earlier.
--
-- We create the transaction body using the 'TransactionBodyContent' that we created earlier:
--
-- @
-- let (Right txBody) = Api.createTransactionBody sbe txBodyContent
-- @
--
-- Then, we sign the transaction using the 'signShelleyTransaction' function and the witness:
--
-- @
-- let oldApiSignedTx :: Api.Tx Api.ConwayEra = Api.signShelleyTransaction sbe txBody [witness]
-- @
--
-- And that is it. We have a signed transaction.

-- ** Creating a transaction using the new API

-- |
-- Now, let's see how we can create a transaction using the new API. First, we create an 'UnsignedTx' using the 'makeUnsignedTx'
-- function and the 'Era' and 'TxBodyContent' that we defined earlier:
--
-- @
-- let (Right unsignedTx) = Exp.makeUnsignedTx era txBodyContent
-- @
--
-- Then we use the key witness to witness the current unsigned transaction using the 'makeKeyWitness' function:
--
-- @
-- let transactionWitness = Exp.makeKeyWitness era unsignedTx (Api.WitnessPaymentKey signingKey)
-- @
--
-- Finally, we sign the transaction using the 'signTx' function:
--
-- @
-- let newApiSignedTx :: Ledger.Tx (Exp.LedgerEra Exp.ConwayEra) = Exp.signTx era [] [transactionWitness] unsignedTx
-- @
palas marked this conversation as resolved.
Show resolved Hide resolved
--
-- Where the empty list is for the bootstrap witnesses, which, in this case, we don't have any.
--
-- And that is it. We have a signed transaction.

-- ** Converting a transaction from the new API to the old API

-- |
-- If we have a transaction created using the new API, we can convert it to the old API very easily by
-- just wrapping it using the 'ShelleyTx' constructor:
--
-- @
-- let oldStyleTx :: Api.Tx Api.ConwayEra = ShelleyTx sbe newApiSignedTx
-- @

-- ** Inspecting transactions

-- |
-- For deconstructing an old style 'TxBody' into a 'TxBodyContent', we can also use the
-- 'TxBody' pattern, but this cannot be used for constructing. For that we use 'ShelleyTxBody'
-- or 'createTransactionBody', like in the example.
--
-- For extracting the 'TxBody' and the 'KeyWitness'es from an old style 'Tx', we can use
-- the lenses 'getTxBody' and 'getTxWitnesses' respectively, from 'Cardano.Api'.
--
-- When using a 'Tx' created using the experimental API, you can extract the 'TxBody' and
-- 'TxWits' using the lenses 'txBody' and 'txWits' respectively, from 'Cardano.Api.Ledger'.

-- * Contents

-- ** Tx related
UnsignedTx (..)
, UnsignedTxError (..)
, makeUnsignedTx
Expand All @@ -12,7 +219,8 @@ module Cardano.Api.Experimental
, obtainCommonConstraints
, hashTxBody
, evaluateTransactionExecutionUnitsShelley
-- Era related

-- ** Era related
, BabbageEra
, ConwayEra
, Era (..)
Expand Down
126 changes: 126 additions & 0 deletions cardano-api/test/cardano-api-test/Test/Cardano/Api/Experimental.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

module Test.Cardano.Api.Experimental
( tests
)
where

import qualified Cardano.Api as Api
import Cardano.Api.Eon.ShelleyBasedEra (ShelleyBasedEraConstraints)
import qualified Cardano.Api.Experimental as Exp
import qualified Cardano.Api.Ledger as Ledger
import qualified Cardano.Api.Script as Script
import Cardano.Api.Tx.Sign (Tx (ShelleyTx))

import Lens.Micro ((&))

import Hedgehog (Property)
import qualified Hedgehog as H
import qualified Hedgehog.Extras as H
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)

-- | Tests in this module can be run by themselves by writing:
-- ```bash
-- cabal test cardano-api-test --test-options="--pattern=Test.Cardano.Api.Experimental"
palas marked this conversation as resolved.
Show resolved Hide resolved
-- ```
--
-- IMPORTANT NOTE: If this file requires changes, please update the examples in the
-- documentation in 'cardano-api/src/Cardano/Api/Experimental.hs' too.
tests :: TestTree
tests =
testGroup
"Test.Cardano.Api.Experimental"
[ testProperty
"Created transaction with traditional and experimental APIs are equivalent"
prop_created_transaction_with_both_apis_are_the_same
]

prop_created_transaction_with_both_apis_are_the_same :: Property
prop_created_transaction_with_both_apis_are_the_same = H.propertyOnce $ do
let era = Exp.ConwayEra
let sbe = Api.convert era

signedTxTraditional <- exampleTransacitonTraditionalWay sbe
signedTxExperimental <- exampleTransactionExperimentalWay era sbe

let oldStyleTx :: Api.Tx Api.ConwayEra = ShelleyTx sbe signedTxExperimental

oldStyleTx H.=== signedTxTraditional
where
exampleTxBodyContent
:: (ShelleyBasedEraConstraints era, H.MonadTest m)
=> Api.AsType era
-> Api.ShelleyBasedEra era
-> m (Api.TxBodyContent Api.BuildTx era)
exampleTxBodyContent eraAsType sbe = do
srcTxId <-
H.evalEither $
Api.deserialiseFromRawBytesHex
Api.AsTxId
"be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd978"
let srcTxIx = Api.TxIx 0
destAddress <-
H.evalMaybe $
Api.deserialiseAddress
(Api.AsAddressInEra eraAsType)
"addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v"

let txBodyContent =
Api.defaultTxBodyContent sbe
& Api.setTxIns
[
( Api.TxIn srcTxId srcTxIx
, Api.BuildTxWith (Api.KeyWitness Api.KeyWitnessForSpending)
)
]
& Api.setTxOuts
[ Api.TxOut
destAddress
(Api.TxOutValueShelleyBased sbe (Api.inject (Ledger.Coin 10_000_000)))
Api.TxOutDatumNone
Script.ReferenceScriptNone
]
& Api.setTxFee (Api.TxFeeExplicit sbe (Ledger.Coin 2_000_000))

return txBodyContent

exampleSigningKey :: H.MonadTest m => m (Api.SigningKey Api.PaymentKey)
exampleSigningKey =
H.evalEither $
Api.deserialiseFromBech32
(Api.AsSigningKey Api.AsPaymentKey)
"addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms"

exampleTransacitonTraditionalWay
:: H.MonadTest m => Api.ShelleyBasedEra Exp.ConwayEra -> m (Tx Exp.ConwayEra)
exampleTransacitonTraditionalWay sbe = do
txBodyContent <- exampleTxBodyContent Api.AsConwayEra sbe
signingKey <- exampleSigningKey

txBody <- H.evalEither $ Api.createTransactionBody sbe txBodyContent

let signedTx :: Api.Tx Api.ConwayEra = Api.signShelleyTransaction sbe txBody [Api.WitnessPaymentKey signingKey]

return signedTx

exampleTransactionExperimentalWay
:: H.MonadTest m
=> Exp.Era Exp.ConwayEra
-> Api.ShelleyBasedEra Exp.ConwayEra
-> m (Ledger.Tx (Exp.LedgerEra Exp.ConwayEra))
exampleTransactionExperimentalWay era sbe = do
txBodyContent <- exampleTxBodyContent Api.AsConwayEra sbe
signingKey <- exampleSigningKey

unsignedTx <- H.evalEither $ Exp.makeUnsignedTx era txBodyContent
let witness = Exp.makeKeyWitness era unsignedTx (Api.WitnessPaymentKey signingKey)

let bootstrapWitnesses = []
keyWitnesses = [witness]

let signedTx :: Ledger.Tx (Exp.LedgerEra Exp.ConwayEra) = Exp.signTx era bootstrapWitnesses keyWitnesses unsignedTx
return signedTx
2 changes: 2 additions & 0 deletions cardano-api/test/cardano-api-test/cardano-api-test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import qualified Test.Cardano.Api.Crypto
import qualified Test.Cardano.Api.Envelope
import qualified Test.Cardano.Api.EpochLeadership
import qualified Test.Cardano.Api.Eras
import qualified Test.Cardano.Api.Experimental
import qualified Test.Cardano.Api.Genesis
import qualified Test.Cardano.Api.IO
import qualified Test.Cardano.Api.Json
Expand Down Expand Up @@ -48,6 +49,7 @@ tests =
, Test.Cardano.Api.Envelope.tests
, Test.Cardano.Api.EpochLeadership.tests
, Test.Cardano.Api.Eras.tests
, Test.Cardano.Api.Experimental.tests
, Test.Cardano.Api.Genesis.tests
, Test.Cardano.Api.IO.tests
, Test.Cardano.Api.Json.tests
Expand Down
Loading