Skip to content

Commit

Permalink
Add minting and burning API, stubbed implementation
Browse files Browse the repository at this point in the history
- Add a new "mint" endpoint under the "assets" API group.
- Add a number of new API types to represent the data the user must submit with
  a mint/burn request, and the data they receive back.
- Adjust swagger specification to match new API types.
- Adds Arbitrary instances for API types for property tests.
- Adds a stub for the minting and burning implementation.
  • Loading branch information
sevanspowell committed Jun 15, 2021
1 parent 08f4889 commit c4a2458
Show file tree
Hide file tree
Showing 8 changed files with 603 additions and 4 deletions.
18 changes: 16 additions & 2 deletions lib/core/src/Cardano/Wallet/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module Cardano.Wallet.Api
, ListAssets
, GetAsset
, GetAssetDefault
, MintBurnAsset

, Addresses
, ListAddresses
Expand Down Expand Up @@ -168,6 +169,7 @@ import Cardano.Wallet.Api.Types
, ApiHealthCheck
, ApiMaintenanceAction
, ApiMaintenanceActionPostData
, ApiMintBurnTransactionT
, ApiNetworkClock
, ApiNetworkInformation
, ApiNetworkParameters
Expand Down Expand Up @@ -199,6 +201,7 @@ import Cardano.Wallet.Api.Types
, KeyFormat
, MinWithdrawal
, PostExternalTransactionData
, PostMintBurnAssetDataT
, PostTransactionDataT
, PostTransactionFeeDataT
, SettingsPutData
Expand Down Expand Up @@ -282,7 +285,7 @@ type ApiV2 n apiPool = "v2" :> Api n apiPool
type Api n apiPool =
Wallets
:<|> WalletKeys
:<|> Assets
:<|> Assets n
:<|> Addresses n
:<|> CoinSelections n
:<|> Transactions n
Expand Down Expand Up @@ -413,10 +416,11 @@ type GetAccountKey = "wallets"
See also: https://input-output-hk.github.io/cardano-wallet/api/#tag/Assets
-------------------------------------------------------------------------------}

type Assets =
type Assets n =
ListAssets
:<|> GetAsset
:<|> GetAssetDefault
:<|> MintBurnAsset n

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/listAssets
type ListAssets = "wallets"
Expand All @@ -439,6 +443,16 @@ type GetAssetDefault = "wallets"
:> Capture "policyId" (ApiT TokenPolicyId)
:> Get '[JSON] ApiAsset

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/mintBurnToken
type MintBurnAsset n = "wallets"
:> Capture "walletId" (ApiT WalletId)
-- :> "assets"
-- :> "mint"
-- This has been changed to prevent overlapping routes:
:> "assets-mint"
:> ReqBody '[JSON] (PostMintBurnAssetDataT n)
:> PostAccepted '[JSON] (ApiMintBurnTransactionT n)

{-------------------------------------------------------------------------------
Addresses
Expand Down
11 changes: 11 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Link.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module Cardano.Wallet.Api.Link
, getAsset
, listByronAssets
, getByronAsset
, mintToken

-- * Transactions
, createTransaction
Expand Down Expand Up @@ -881,3 +882,13 @@ instance HasVerb sub => HasVerb (QueryFlag sym :> sub) where

instance HasVerb sub => HasVerb (Header' opts name ty :> sub) where
method _ = method (Proxy @sub)

mintToken
:: forall w.
( HasType (ApiT WalletId) w
)
=> w
-> (Method, Text)
mintToken w = (endpoint @(Api.MintBurnAsset Net) (wid &))
where
wid = w ^. typed @(ApiT WalletId)
12 changes: 12 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ module Cardano.Wallet.Api.Server
, postSharedWallet
, patchSharedWallet
, mkSharedWallet
, mintToken

-- * Server error responses
, IsServerError(..)
Expand Down Expand Up @@ -199,6 +200,7 @@ import Cardano.Wallet.Api.Types
, ApiErrorCode (..)
, ApiFee (..)
, ApiForeignStakeKey (..)
, ApiMintBurnTransaction (..)
, ApiMnemonicT (..)
, ApiNetworkClock (..)
, ApiNetworkInformation
Expand Down Expand Up @@ -3659,3 +3661,13 @@ instance HasSeverityAnnotation WalletEngineLog where
getSeverityAnnotation = \case
MsgWalletWorker msg -> getSeverityAnnotation msg
MsgSubmitSealedTx msg -> getSeverityAnnotation msg

mintToken
:: forall ctx s n
. ctx
-> ArgGenChange s
-> ApiT WalletId
-> Api.PostMintBurnAssetData n
-> Handler (ApiMintBurnTransaction n)
mintToken _ctx _genChange (ApiT _wid) _body =
error "Minting is not supported - this is just a stub"
157 changes: 157 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ module Cardano.Wallet.Api.Types
, ApiSelectCoinsPayments (..)
, ApiSelectCoinsAction (..)
, ApiCoinSelection (..)
, ApiMintBurnOperation (..)
, ApiMintData(..)
, ApiBurnData(..)
, ApiCoinSelectionChange (..)
, ApiCoinSelectionInput (..)
, ApiCoinSelectionOutput (..)
, ApiCoinSelectionWithdrawal (..)
, ApiMintBurnData (..)
, ApiRawMetadata (..)
, ApiStakePool (..)
, ApiStakePoolMetrics (..)
Expand All @@ -90,6 +94,7 @@ module Cardano.Wallet.Api.Types
, PostTransactionFeeData (..)
, PostExternalTransactionData (..)
, ApiTransaction (..)
, ApiMintBurnTransaction (..)
, ApiWithdrawalPostData (..)
, ApiMaintenanceAction (..)
, ApiMaintenanceActionPostData (..)
Expand Down Expand Up @@ -140,6 +145,7 @@ module Cardano.Wallet.Api.Types
, KeyFormat (..)
, ApiPostAccountKeyData (..)
, ApiPostAccountKeyDataWithPurpose (..)
, PostMintBurnAssetData(..)

-- * API Types (Byron)
, ApiByronWallet (..)
Expand Down Expand Up @@ -185,10 +191,12 @@ module Cardano.Wallet.Api.Types
, ApiCoinSelectionT
, ApiSelectCoinsDataT
, ApiTransactionT
, ApiMintBurnTransactionT
, PostTransactionDataT
, PostTransactionFeeDataT
, ApiWalletMigrationPlanPostDataT
, ApiWalletMigrationPostDataT
, PostMintBurnAssetDataT

-- * API Type Conversions
, coinToQuantity
Expand Down Expand Up @@ -927,6 +935,24 @@ data ApiTransaction (n :: NetworkDiscriminant) = ApiTransaction
} deriving (Eq, Generic, Show)
deriving anyclass NFData

-- | The response cardano-wallet returns upon successful submission of a
-- mint/burn transaction.
data ApiMintBurnTransaction (n :: NetworkDiscriminant) = ApiMintBurnTransaction
{ transaction :: !(ApiTransaction n)
-- ^ Information about the mint/burn transaction.
, monetaryPolicyIndex :: !(ApiT DerivationIndex)
-- ^ The monetary policy index the asset was minted/burnt under.
, policyId :: !(ApiT W.TokenPolicyId)
-- ^ The policy ID the asset was minted/burnt under.
, assetName :: !(ApiT W.TokenName)
-- ^ The name of the asset minted/burnt.
, subject :: !(ApiT W.TokenFingerprint)
-- ^ The subject of the asset minted/burnt. This is useful to users wishing
-- to attach metadata to their asset.
}
deriving (Eq, Generic, Show)
deriving anyclass NFData

newtype ApiTxMetadata = ApiTxMetadata
{ getApiTxMetadata :: Maybe (ApiT TxMetadata)
} deriving (Eq, Generic, Show)
Expand Down Expand Up @@ -2342,6 +2368,18 @@ instance DecodeAddress t => FromJSON (PostTransactionData t) where
instance EncodeAddress t => ToJSON (PostTransactionData t) where
toJSON = genericToJSON defaultRecordTypeOptions

instance
( DecodeAddress n
, DecodeStakeAddress n
) => FromJSON (ApiMintBurnTransaction n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance
( EncodeAddress n
, EncodeStakeAddress n
) => ToJSON (ApiMintBurnTransaction n) where
toJSON = genericToJSON defaultRecordTypeOptions

instance FromJSON ApiWithdrawalPostData where
parseJSON obj =
parseSelfWithdrawal <|> fmap ExternalWithdrawal (parseJSON obj)
Expand Down Expand Up @@ -2995,7 +3033,9 @@ type family ApiAddressIdT (n :: k) :: Type
type family ApiCoinSelectionT (n :: k) :: Type
type family ApiSelectCoinsDataT (n :: k) :: Type
type family ApiTransactionT (n :: k) :: Type
type family ApiMintBurnTransactionT (n :: k) :: Type
type family PostTransactionDataT (n :: k) :: Type
type family PostMintBurnAssetDataT (n :: k) :: Type
type family PostTransactionFeeDataT (n :: k) :: Type
type family ApiWalletMigrationPlanPostDataT (n :: k) :: Type
type family ApiWalletMigrationPostDataT (n :: k1) (s :: k2) :: Type
Expand Down Expand Up @@ -3025,6 +3065,9 @@ type instance ApiTransactionT (n :: NetworkDiscriminant) =
type instance PostTransactionDataT (n :: NetworkDiscriminant) =
PostTransactionData n

type instance PostMintBurnAssetDataT (n :: NetworkDiscriminant) =
PostMintBurnAssetData n

type instance PostTransactionFeeDataT (n :: NetworkDiscriminant) =
PostTransactionFeeData n

Expand All @@ -3034,6 +3077,9 @@ type instance ApiWalletMigrationPlanPostDataT (n :: NetworkDiscriminant) =
type instance ApiWalletMigrationPostDataT (n :: NetworkDiscriminant) (s :: Symbol) =
ApiWalletMigrationPostData n s

type instance ApiMintBurnTransactionT (n :: NetworkDiscriminant) =
ApiMintBurnTransaction n

{-------------------------------------------------------------------------------
SMASH interfacing types
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -3076,3 +3122,114 @@ instance FromJSON (ApiT SmashServer) where
parseJSON = fromTextJSON "SmashServer"
instance ToJSON (ApiT SmashServer) where
toJSON = toTextJSON

{-------------------------------------------------------------------------------
Token minting types
-------------------------------------------------------------------------------}

-- | Data required when submitting a mint/burn transaction. Cardano implements
-- minting and burning using transactions, so some of these fields are shared
-- with @PostTransactionData@.
data PostMintBurnAssetData (n :: NetworkDiscriminant) = PostMintBurnAssetData
{ mintBurn :: !(ApiMintBurnData n)
-- ^ Minting and burning request.
, passphrase :: !(ApiT (Passphrase "lenient"))
-- ^ Passphrase of the wallet.
, metadata :: !(Maybe (ApiT TxMetadata))
-- ^ Metadata to attach to the transaction that mints/burns.
, timeToLive :: !(Maybe (Quantity "second" NominalDiffTime))
-- ^ Time the created mint/burn transaction is valid until.
} deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (PostMintBurnAssetData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (PostMintBurnAssetData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | Core minting and burning request information.
--
-- Assets are minted and burned under a "policy". The policy defines under what
-- circumstances a token may be minted and burned. The typical policy is "A
-- token may be minted and burned if signature 's' witnesses the transaction,
-- for some signature 's'". This is the only type of policy supported by the
-- cardano-wallet API at the moment. Because cardano-wallet manages the keys of
-- the user, we ask the user not for a specific signature, but rather for a key
-- derivation index, which we use to derive the signature to construct the
-- policy with.
data ApiMintBurnData (n :: NetworkDiscriminant) = ApiMintBurnData
{ monetaryPolicyIndex :: !(Maybe (ApiT DerivationIndex))
-- ^ The key derivation index to use to construct the policy.
, assetName :: !(ApiT W.TokenName)
-- ^ The name of the asset to mint/burn.
, operations :: !(NonEmpty (ApiMintBurnOperation n))
-- ^ The set of minting and burning operations to perform.
} deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (ApiMintBurnData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (ApiMintBurnData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | A user may choose to either mint tokens or burn tokens with each operation.
data ApiMintBurnOperation (n :: NetworkDiscriminant)
= ApiMint (ApiMintData n)
-- ^ Mint tokens.
| ApiBurn ApiBurnData
-- ^ Burn tokens.
deriving (Eq, Generic, Show)

-- | The format of a minting request: mint "amount" and send it to the
-- "address".
data ApiMintData (n :: NetworkDiscriminant) = ApiMintData
{ receivingAddress :: (ApiT Address, Proxy n)
-- ^ Address that receives the minted assets.
, amount :: Quantity "assets" Natural
-- ^ Amount of assets to mint.
}
deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (ApiMintData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (ApiMintData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | The format of a burn request: burn "amount". The user can only specify the
-- type of tokens to burn (policyId, assetName), and the amount, the exact
-- tokens selected are up to the implementation.
newtype ApiBurnData = ApiBurnData (Quantity "assets" Natural)
deriving (Eq, Generic, Show)

instance FromJSON ApiBurnData where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance ToJSON ApiBurnData where
toJSON (burn) = genericToJSON defaultRecordTypeOptions burn

instance EncodeAddress n => ToJSON (ApiMintBurnOperation n) where
toJSON = \case
ApiMint mint ->
Aeson.Object $ HM.singleton "mint" (toJSON mint)
ApiBurn burn ->
Aeson.Object $ HM.singleton "burn" (toJSON burn)

instance DecodeAddress n => FromJSON (ApiMintBurnOperation n) where
parseJSON = Aeson.withObject "ApiMintBurnOperation" $ \o -> do
mMints <- o .:? "mint"
mBurn <- o .:? "burn"

let badKeys = filter (\k -> k /= "mint" && k /= "burn") . HM.keys $ o

if badKeys /= []
then fail $ "Encountered unexpected key(s): " <> show badKeys
else case (mMints, mBurn) of
(Nothing , Nothing) ->
fail "Must include a mint or burn operation"
(Just mints , Nothing) ->
pure $ ApiMint mints
(Nothing , Just burn) ->
pure $ ApiBurn burn
(Just _mints , Just _burn) ->
fail "Each operation may either be a mint or a burn, not both"
Loading

0 comments on commit c4a2458

Please sign in to comment.