From 36665a6c86500459900cf35368635e48cbd6d1fe Mon Sep 17 00:00:00 2001 From: Samuel Evans-Powell Date: Tue, 15 Jun 2021 13:30:39 +0800 Subject: [PATCH 1/2] Add minting and burning API, stubbed implementation - 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. --- lib/core/src/Cardano/Wallet.hs | 6 + lib/core/src/Cardano/Wallet/Api.hs | 17 +- lib/core/src/Cardano/Wallet/Api/Link.hs | 11 + lib/core/src/Cardano/Wallet/Api/Server.hs | 17 ++ lib/core/src/Cardano/Wallet/Api/Types.hs | 170 +++++++++++++++ .../test/unit/Cardano/Wallet/Api/Malformed.hs | 200 ++++++++++++++++++ .../test/unit/Cardano/Wallet/Api/TypesSpec.hs | 87 ++++++++ .../src/Cardano/Wallet/Shelley/Api/Server.hs | 9 +- specifications/api/swagger.yaml | 158 ++++++++++++++ 9 files changed, 670 insertions(+), 5 deletions(-) diff --git a/lib/core/src/Cardano/Wallet.hs b/lib/core/src/Cardano/Wallet.hs index 4cabe5bae48..f564adac646 100644 --- a/lib/core/src/Cardano/Wallet.hs +++ b/lib/core/src/Cardano/Wallet.hs @@ -123,6 +123,7 @@ module Cardano.Wallet , ErrNotASequentialWallet (..) , ErrWithdrawalNotWorth (..) , ErrConstructTx (..) + , ErrMintBurnAssets (..) -- ** Migration , createMigrationPlan @@ -2595,6 +2596,11 @@ data ErrConstructTx -- ^ Temporary error constructor. deriving (Show, Eq) +newtype ErrMintBurnAssets + = ErrMintBurnNotImplemented T.Text + -- ^ Temporary error constructor. + deriving (Show, Eq) + -- | Errors that can occur when submitting a signed transaction to the network. data ErrSubmitTx = ErrSubmitTxNetwork ErrPostTx diff --git a/lib/core/src/Cardano/Wallet/Api.hs b/lib/core/src/Cardano/Wallet/Api.hs index a47036a829e..87d851e46f7 100644 --- a/lib/core/src/Cardano/Wallet/Api.hs +++ b/lib/core/src/Cardano/Wallet/Api.hs @@ -40,6 +40,7 @@ module Cardano.Wallet.Api , ListAssets , GetAsset , GetAssetDefault + , MintBurnAssets , Addresses , ListAddresses @@ -173,6 +174,7 @@ import Cardano.Wallet.Api.Types , ApiHealthCheck , ApiMaintenanceAction , ApiMaintenanceActionPostData + , ApiMintedBurnedTransactionT , ApiNetworkClock , ApiNetworkInformation , ApiNetworkParameters @@ -206,6 +208,7 @@ import Cardano.Wallet.Api.Types , Iso8601Time , KeyFormat , MinWithdrawal + , PostMintBurnAssetDataT , PostTransactionFeeOldDataT , PostTransactionOldDataT , SettingsPutData @@ -291,7 +294,7 @@ type ApiV2 n apiPool = "v2" :> Api n apiPool type Api n apiPool = Wallets :<|> WalletKeys - :<|> Assets + :<|> Assets n :<|> Addresses n :<|> CoinSelections n :<|> ShelleyTransactions n @@ -422,8 +425,9 @@ type GetAccountKey = "wallets" See also: https://input-output-hk.github.io/cardano-wallet/api/#tag/Assets -------------------------------------------------------------------------------} -type Assets = - ListAssets +type Assets n = + MintBurnAssets n + :<|> ListAssets :<|> GetAsset :<|> GetAssetDefault @@ -448,6 +452,13 @@ type GetAssetDefault = "wallets" :> Capture "policyId" (ApiT TokenPolicyId) :> Get '[JSON] ApiAsset +-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/mintBurnAssets +type MintBurnAssets n = "wallets" + :> Capture "walletId" (ApiT WalletId) + :> "assets" + :> ReqBody '[JSON] (PostMintBurnAssetDataT n) + :> PostAccepted '[JSON] (ApiMintedBurnedTransactionT n) + {------------------------------------------------------------------------------- Addresses diff --git a/lib/core/src/Cardano/Wallet/Api/Link.hs b/lib/core/src/Cardano/Wallet/Api/Link.hs index dc7960ecfe1..e6bbceacda3 100644 --- a/lib/core/src/Cardano/Wallet/Api/Link.hs +++ b/lib/core/src/Cardano/Wallet/Api/Link.hs @@ -71,6 +71,7 @@ module Cardano.Wallet.Api.Link , getAsset , listByronAssets , getByronAsset + , mintBurnAssets -- * Transactions , createTransaction @@ -897,3 +898,13 @@ instance HasVerb sub => HasVerb (QueryFlag sym :> sub) where instance HasVerb sub => HasVerb (Header' opts name ty :> sub) where method _ = method (Proxy @sub) + +mintBurnAssets + :: forall w. + ( HasType (ApiT WalletId) w + ) + => w + -> (Method, Text) +mintBurnAssets w = (endpoint @(Api.MintBurnAssets Net) (wid &)) + where + wid = w ^. typed @(ApiT WalletId) diff --git a/lib/core/src/Cardano/Wallet/Api/Server.hs b/lib/core/src/Cardano/Wallet/Api/Server.hs index 8d2a8b0789b..403f7715b80 100644 --- a/lib/core/src/Cardano/Wallet/Api/Server.hs +++ b/lib/core/src/Cardano/Wallet/Api/Server.hs @@ -89,6 +89,7 @@ module Cardano.Wallet.Api.Server , postSharedWallet , patchSharedWallet , mkSharedWallet + , mintBurnAssets -- * Server error responses , IsServerError(..) @@ -142,6 +143,7 @@ import Cardano.Wallet , ErrJoinStakePool (..) , ErrListTransactions (..) , ErrListUTxOStatistics (..) + , ErrMintBurnAssets (..) , ErrMkTx (..) , ErrNoSuchTransaction (..) , ErrNoSuchWallet (..) @@ -205,6 +207,7 @@ import Cardano.Wallet.Api.Types , ApiErrorCode (..) , ApiFee (..) , ApiForeignStakeKey (..) + , ApiMintedBurnedTransaction (..) , ApiMnemonicT (..) , ApiNetworkClock (..) , ApiNetworkInformation @@ -2640,6 +2643,16 @@ getAccountPublicKey ctx mkAccount (ApiT wid) extended = do Just Extended -> Extended _ -> NonExtended +mintBurnAssets + :: forall ctx n + . ctx + -> ApiT WalletId + -> Api.PostMintBurnAssetData n + -> Handler (ApiMintedBurnedTransaction n) +mintBurnAssets _ctx (ApiT _wid) _body = liftHandler $ throwE $ + ErrMintBurnNotImplemented + "Minting and burning are not supported yet - this is just a stub" + {------------------------------------------------------------------------------- Helpers -------------------------------------------------------------------------------} @@ -3305,6 +3318,10 @@ instance IsServerError ErrConstructTx where apiError err501 NotImplemented "This feature is not yet implemented." +instance IsServerError ErrMintBurnAssets where + toServerError = \case + ErrMintBurnNotImplemented msg -> apiError err501 NotImplemented msg + instance IsServerError ErrDecodeSignedTx where toServerError = \case ErrDecodeSignedTxWrongPayload _ -> diff --git a/lib/core/src/Cardano/Wallet/Api/Types.hs b/lib/core/src/Cardano/Wallet/Api/Types.hs index 8ea07d5f056..d3a5ae3f0bb 100644 --- a/lib/core/src/Cardano/Wallet/Api/Types.hs +++ b/lib/core/src/Cardano/Wallet/Api/Types.hs @@ -66,11 +66,15 @@ module Cardano.Wallet.Api.Types , ApiSelectCoinsPayments (..) , ApiSelectCoinsAction (..) , ApiCoinSelection (..) + , ApiMintBurnOperation (..) + , ApiMintData(..) + , ApiBurnData(..) , ApiCoinSelectionChange (..) , ApiCoinSelectionInput (..) , ApiCoinSelectionOutput (..) , ApiCoinSelectionWithdrawal (..) , ApiBase64 + , ApiMintBurnData (..) , ApiStakePool (..) , ApiStakePoolMetrics (..) , ApiStakePoolFlag (..) @@ -93,6 +97,8 @@ module Cardano.Wallet.Api.Types , ApiSerialisedTransaction (..) , ApiSignedTransaction (..) , ApiTransaction (..) + , ApiMintedBurnedTransaction (..) + , ApiMintedBurnedInfo (..) , ApiWithdrawalPostData (..) , ApiMaintenanceAction (..) , ApiMaintenanceActionPostData (..) @@ -150,6 +156,7 @@ module Cardano.Wallet.Api.Types , ApiPaymentDestination (..) , ApiValidityInterval (..) , ApiValidityBound + , PostMintBurnAssetData(..) -- * API Types (Byron) , ApiByronWallet (..) @@ -200,8 +207,10 @@ module Cardano.Wallet.Api.Types , ApiConstructTransactionDataT , PostTransactionOldDataT , PostTransactionFeeOldDataT + , ApiMintedBurnedTransactionT , ApiWalletMigrationPlanPostDataT , ApiWalletMigrationPostDataT + , PostMintBurnAssetDataT -- * API Type Conversions , coinToQuantity @@ -1053,6 +1062,33 @@ 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 ApiMintedBurnedTransaction (n :: NetworkDiscriminant) = ApiMintedBurnedTransaction + { transaction :: !(ApiTransaction n) + -- ^ Information about the mint/burn transaction itself. + , mintedBurned :: !(NonEmpty (ApiT ApiMintedBurnedInfo)) + -- ^ Helpful information about each unique asset minted or burned (where the + -- identity is the policyId + asset name of the asset). + } + deriving (Eq, Generic, Show) + deriving anyclass NFData + +data ApiMintedBurnedInfo = ApiMintedBurnedInfo + { 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. + , script :: !(ApiT (Script KeyHash)) + -- ^ The script which this asset was minted and/or burned under + } deriving (Eq, Generic, Show) + deriving anyclass NFData + newtype ApiTxMetadata = ApiTxMetadata { getApiTxMetadata :: Maybe (ApiT TxMetadata) } @@ -2606,6 +2642,18 @@ instance (DecodeAddress t, DecodeStakeAddress t) => FromJSON (ApiConstructTransa instance (EncodeAddress t, EncodeStakeAddress t) => ToJSON (ApiConstructTransaction t) where toJSON = genericToJSON defaultRecordTypeOptions +instance + ( DecodeAddress n + , DecodeStakeAddress n + ) => FromJSON (ApiMintedBurnedTransaction n) where + parseJSON = genericParseJSON defaultRecordTypeOptions + +instance + ( EncodeAddress n + , EncodeStakeAddress n + ) => ToJSON (ApiMintedBurnedTransaction n) where + toJSON = genericToJSON defaultRecordTypeOptions + instance FromJSON ApiWithdrawalPostData where parseJSON obj = parseSelfWithdrawal <|> fmap ExternalWithdrawal (parseJSON obj) @@ -3305,6 +3353,8 @@ type family ApiConstructTransactionT (n :: k) :: Type type family ApiConstructTransactionDataT (n :: k) :: Type type family PostTransactionOldDataT (n :: k) :: Type type family PostTransactionFeeOldDataT (n :: k) :: Type +type family ApiMintedBurnedTransactionT (n :: k) :: Type +type family PostMintBurnAssetDataT (n :: k) :: Type type family ApiWalletMigrationPlanPostDataT (n :: k) :: Type type family ApiWalletMigrationPostDataT (n :: k1) (s :: k2) :: Type type family ApiPutAddressesDataT (n :: k) :: Type @@ -3341,12 +3391,18 @@ type instance PostTransactionOldDataT (n :: NetworkDiscriminant) = type instance PostTransactionFeeOldDataT (n :: NetworkDiscriminant) = PostTransactionFeeOldData n +type instance PostMintBurnAssetDataT (n :: NetworkDiscriminant) = + PostMintBurnAssetData n + type instance ApiWalletMigrationPlanPostDataT (n :: NetworkDiscriminant) = ApiWalletMigrationPlanPostData n type instance ApiWalletMigrationPostDataT (n :: NetworkDiscriminant) (s :: Symbol) = ApiWalletMigrationPostData n s +type instance ApiMintedBurnedTransactionT (n :: NetworkDiscriminant) = + ApiMintedBurnedTransaction n + {------------------------------------------------------------------------------- SMASH interfacing types -------------------------------------------------------------------------------} @@ -3390,3 +3446,117 @@ 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 :: !(NonEmpty (ApiMintBurnData n)) + -- ^ Minting and burning requests. + , 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. + , operation :: !(ApiMintBurnOperation n) + -- ^ The minting or burning operation 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 = object . pure . \case + ApiMint mint -> "mint" .= mint + ApiBurn burn -> "burn" .= burn + +instance DecodeAddress n => FromJSON (ApiMintBurnOperation n) where + parseJSON = Aeson.withObject "ApiMintBurnOperation" $ \o -> + case HM.keys o of + ["mint"] -> ApiMint <$> o .: "mint" + ["burn"] -> ApiBurn <$> o .: "burn" + [] -> fail "Must include a \"mint\" or \"burn\" property." + _ -> fail "May be either a \"mint\" or a \"burn\"." + +instance FromJSON ApiMintedBurnedInfo where + parseJSON = genericParseJSON defaultRecordTypeOptions + +instance ToJSON ApiMintedBurnedInfo where + toJSON = genericToJSON defaultRecordTypeOptions + +instance FromJSON (ApiT ApiMintedBurnedInfo) where + parseJSON = fmap ApiT . parseJSON +instance ToJSON (ApiT ApiMintedBurnedInfo) where + toJSON = toJSON . getApiT + +instance FromJSON (ApiT (Script KeyHash)) where + parseJSON = fmap ApiT . parseJSON +instance ToJSON (ApiT (Script KeyHash)) where + toJSON = toJSON . getApiT diff --git a/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs b/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs index 7bbf6c62527..3651ce64f14 100644 --- a/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs +++ b/lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs @@ -73,6 +73,7 @@ import Cardano.Wallet.Api.Types , ApiWalletSignData , Base (Base64) , ByronWalletPutPassphraseData + , PostMintBurnAssetData , PostTransactionFeeOldData , PostTransactionOldData , SettingsPutData (..) @@ -1938,3 +1939,202 @@ putAddressesDataCases = , "Error in $: parsing Cardano.Wallet.Api.Types.ApiPutAddressesData(ApiPutAddressesData) failed, key 'addresses' not found" ) ] + +instance Malformed (BodyParam (PostMintBurnAssetData ('Testnet pm))) where + malformed = jsonValid ++ jsonInvalid + where + jsonInvalid = first BodyParam <$> + [ ("1020344", "Error in $: parsing Cardano.Wallet.Api.Types.PostMintBurnAssetData(PostMintBurnAssetData) failed, expected Object, but encountered Number") + , ("\"1020344\"", "Error in $: parsing Cardano.Wallet.Api.Types.PostMintBurnAssetData(PostMintBurnAssetData) failed, expected Object, but encountered String") + , ("{\"mint_burn: {}, \"random\"}", msgJsonInvalid) + ] + jsonValid = first (BodyParam . Aeson.encode) <$> + [ + ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "not a monetary policy index" + , "asset_name": "deadbeef" + , "operation": { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + }] + , "passphrase": "" + }|] + , "Error in $['mint_burn'][0]['monetary_policy_index']: A derivation index must be a natural number between 0 and 2147483647 with an optional 'H' suffix (e.g. '1815H' or '44'). Indexes without suffixes are called 'Soft' Indexes with suffixes are called 'Hardened'." + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $.passphrase: passphrase is too long: expected at most 255 characters" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "-1" + , "asset_name": "deadbeef" + , "operation": [ { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + ] + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0]['monetary_policy_index']: A derivation index must be a natural number between 0 and 2147483647 with an optional 'H' suffix (e.g. '1815H' or '44'). Indexes without suffixes are called 'Soft' Indexes with suffixes are called 'Hardened'." + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "not hexadecimal" + , "operation": [ { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + ] + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0]['asset_name']: 'base16: input: invalid length'" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": 3 + , "operation": [ { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + ] + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0]['asset_name']: parsing AssetName failed, expected String, but encountered Number" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": [] + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation: parsing ApiMintBurnOperation failed, expected Object, but encountered Array" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "mint": {} } + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation.mint: parsing Cardano.Wallet.Api.Types.ApiMintData(ApiMintData) failed, key 'receiving_address' not found" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "not an asset unit" + , "quantity": 3 + } + } + } + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation.mint.amount: failed to parse quantified value. Expected value in 'assets' (e.g. { 'unit': 'assets', 'quantity': ... }) but got something else." + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "mint": { "receiving_address": [] + , "amount": { "unit": "assets" + , "quantity": 3 + } + } + } + + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation.mint['receiving_address']: parsing Text failed, expected String, but encountered Array" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "burn": { "unit": "assets" + , "quantity": -1 + } + } + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation.burn.quantity: parsing Natural failed, unexpected negative number -1" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": -1 + } + } + } + + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation.mint.amount.quantity: parsing Natural failed, unexpected negative number -1" + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "burn": { "unit": "assets" + , "quantity": 1 + } + , "mint": { "receiving_address": #{addrPlaceholder} + , "amount": { "unit": "assets" + , "quantity": 1 + } + } + } + + }] + , "passphrase": #{nameTooLong} + }|] + , "Error in $['mint_burn'][0].operation: May be either a 'mint' or a 'burn'." + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": { "burn": { "unit": "assets" + , "quantity": 1 + } + , "something_else": 3 + } + }] + , "passphrase": "" + }|] + , "Error in $['mint_burn'][0].operation: May be either a 'mint' or a 'burn'." + ) + , ( [aesonQQ| + { "mint_burn": [{ "monetary_policy_index": "0" + , "asset_name": "deadbeef" + , "operation": {} + }] + , "passphrase": "" + }|] + , "Error in $['mint_burn'][0].operation: Must include a 'mint' or 'burn' property." + ) + ] diff --git a/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs b/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs index e99671ead33..7f0fb0a159b 100644 --- a/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs @@ -65,6 +65,7 @@ import Cardano.Wallet.Api.Types , ApiBase64 , ApiBlockInfo (..) , ApiBlockReference (..) + , ApiBurnData (..) , ApiByronWallet (..) , ApiByronWalletBalance (..) , ApiBytesT (..) @@ -87,6 +88,11 @@ import Cardano.Wallet.Api.Types , ApiHealthCheck (..) , ApiMaintenanceAction (..) , ApiMaintenanceActionPostData (..) + , ApiMintBurnData (..) + , ApiMintBurnOperation (..) + , ApiMintData (..) + , ApiMintedBurnedInfo (..) + , ApiMintedBurnedTransaction (..) , ApiMnemonicT (..) , ApiMultiDelegationAction (..) , ApiNetworkClock (..) @@ -158,6 +164,7 @@ import Cardano.Wallet.Api.Types , Iso8601Time (..) , KeyFormat (..) , NtpSyncingStatus (..) + , PostMintBurnAssetData (..) , PostTransactionFeeOldData (..) , PostTransactionOldData (..) , SettingsPutData (..) @@ -248,8 +255,12 @@ import Cardano.Wallet.Primitive.Types.TokenPolicy , AssetMetadata (..) , AssetURL (..) , TokenFingerprint + , TokenName (..) + , TokenPolicyId (..) , mkTokenFingerprint ) +import Cardano.Wallet.Primitive.Types.TokenPolicy.Gen + ( genTokenNameSmallRange ) import Cardano.Wallet.Primitive.Types.Tx ( Direction (..) , SerialisedTx (..) @@ -357,6 +368,7 @@ import Test.QuickCheck , InfiniteList (..) , applyArbitrary2 , applyArbitrary3 + , applyArbitrary4 , arbitraryBoundedEnum , arbitraryPrintableChar , arbitrarySizedBoundedIntegral @@ -516,6 +528,7 @@ spec = parallel $ do jsonRoundtripAndGolden $ Proxy @(ApiOurStakeKey ('Testnet 0)) jsonRoundtripAndGolden $ Proxy @(ApiForeignStakeKey ('Testnet 0)) jsonRoundtripAndGolden $ Proxy @ApiNullStakeKey + jsonRoundtripAndGolden $ Proxy @(PostMintBurnAssetData ('Testnet 0)) describe "Textual encoding" $ do describe "Can perform roundtrip textual encoding & decoding" $ do @@ -995,6 +1008,16 @@ spec = parallel $ do } in x' === x .&&. show x' === show x + it "PostMintBurnAssetData" $ property $ \x -> + let + x' = PostMintBurnAssetData + { mintBurn = mintBurn (x :: PostMintBurnAssetData ('Testnet 0)) + , passphrase = passphrase (x :: PostMintBurnAssetData ('Testnet 0)) + , metadata = metadata (x :: PostMintBurnAssetData ('Testnet 0)) + , timeToLive = timeToLive (x :: PostMintBurnAssetData ('Testnet 0)) + } + in + x' === x .&&. show x' === show x it "PostTransactionOldData" $ property $ \x -> let x' = PostTransactionOldData @@ -1371,6 +1394,11 @@ instance Arbitrary (Quantity "lovelace" Natural) where shrink _ = [Quantity 0] arbitrary = Quantity . fromIntegral <$> (arbitrary @Word8) +instance Arbitrary (Quantity "assets" Natural) where + shrink (Quantity 0) = [] + shrink _ = [Quantity 0] + arbitrary = Quantity . fromIntegral <$> (arbitrary @Word8) + instance Arbitrary (Quantity "percent" Percentage) where shrink (Quantity p) = Quantity <$> shrinkPercentage p arbitrary = Quantity <$> genPercentage @@ -1913,20 +1941,74 @@ instance Arbitrary (ApiConstructTransactionData t) where <*> arbitrary <*> pure Nothing +instance Arbitrary (PostMintBurnAssetData t) where + arbitrary = applyArbitrary4 PostMintBurnAssetData + instance Arbitrary (ApiConstructTransaction t) where arbitrary = ApiConstructTransaction <$> arbitrary <*> arbitrary <*> arbitrary +instance Arbitrary (ApiMintBurnData t) where + arbitrary = ApiMintBurnData + <$> arbitrary + <*> (ApiT <$> genTokenNameSmallRange) + <*> arbitrary + instance Arbitrary ApiStakeKeyIndex where arbitrary = ApiStakeKeyIndex <$> arbitrary +instance Arbitrary (ApiMintData t) where + arbitrary = ApiMintData <$> arbitrary <*> arbitrary + instance Arbitrary (ApiPaymentDestination t) where arbitrary = oneof [ ApiPaymentAddresses <$> arbitrary , ApiPaymentAll <$> arbitrary] +instance Arbitrary ApiBurnData where + arbitrary = ApiBurnData <$> arbitrary + +instance Arbitrary (ApiMintBurnOperation t) where + arbitrary + = oneof [ ApiMint <$> arbitrary + , ApiBurn <$> arbitrary + ] + +instance Arbitrary (ApiMintedBurnedTransaction t) where + arbitrary = ApiMintedBurnedTransaction <$> arbitrary <*> arbitrary + +instance Arbitrary ApiMintedBurnedInfo where + arbitrary = do + mpi <- arbitrary + policyId <- arbitrary + assetName <- arbitrary + let + subject = mkTokenFingerprint policyId assetName + script = + RequireSignatureOf + (KeyHash Payment $ getHash $ unTokenPolicyId policyId) + + pure $ ApiMintedBurnedInfo + (ApiT mpi) + (ApiT policyId) + (ApiT assetName) + (ApiT subject) + (ApiT script) + + +instance ToSchema (ApiMintedBurnedTransaction t) where + declareNamedSchema _ = do + addDefinition =<< declareSchemaForDefinition "TransactionMetadataValue" + declareSchemaForDefinition "ApiMintedBurnedTransaction" + +instance Arbitrary TokenPolicyId where + arbitrary = UnsafeTokenPolicyId . Hash . BS.pack <$> vector 28 + +instance Arbitrary TokenName where + arbitrary = UnsafeTokenName . BS.pack <$> vector 32 + instance Arbitrary ApiWithdrawalPostData where arbitrary = genericArbitrary shrink = genericShrink @@ -2394,6 +2476,11 @@ instance ToSchema (PostTransactionFeeOldData t) where addDefinition =<< declareSchemaForDefinition "TransactionMetadataValue" declareSchemaForDefinition "ApiPostTransactionFeeData" +instance ToSchema (PostMintBurnAssetData t) where + declareNamedSchema _ = do + addDefinition =<< declareSchemaForDefinition "TransactionMetadataValue" + declareSchemaForDefinition "ApiPostMintBurnAssetData" + instance ToSchema (ApiTransaction t) where declareNamedSchema _ = do addDefinition =<< declareSchemaForDefinition "TransactionMetadataValue" diff --git a/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs b/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs index 5dd0a5f72f3..ac807d9524f 100644 --- a/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs +++ b/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs @@ -87,6 +87,7 @@ import Cardano.Wallet.Api.Server , listTransactions , listWallets , migrateWallet + , mintBurnAssets , mkLegacyWallet , mkSharedWallet , mkShelleyWallet @@ -264,8 +265,12 @@ server byron icarus shelley multisig spl ntp = :<|> postAccountPublicKey shelley ApiAccountKey :<|> getAccountPublicKey shelley ApiAccountKey - assets :: Server Assets - assets = listAssets shelley :<|> getAsset shelley :<|> getAssetDefault shelley + assets :: Server (Assets n) + assets = + mintBurnAssets shelley + :<|> listAssets shelley + :<|> getAsset shelley + :<|> getAssetDefault shelley addresses :: Server (Addresses n) addresses = listAddresses shelley (normalizeDelegationAddress @_ @ShelleyKey @n) diff --git a/specifications/api/swagger.yaml b/specifications/api/swagger.yaml index 897e7be9767..8422eafd7b7 100644 --- a/specifications/api/swagger.yaml +++ b/specifications/api/swagger.yaml @@ -2909,6 +2909,107 @@ components: "none": *ApiNullStakeKey + ApiMintedBurnedInfo: &ApiMintedBurnedInfo + type: object + required: + - monetary_policy_index + - policy_id + - asset_name + - subject + - script + properties: + monetary_policy_index: *derivationSegment + policy_id: *assetPolicyId + asset_name: *assetName + subject: *assetFingerprint + script: + <<: *ScriptValue + description: The script under which this asset was minted or burned. + + ApiMintedBurnedTransaction: &ApiMintedBurnedTransaction + type: object + required: + - transaction + - minted_burned + properties: + transaction: + <<: *ApiTransaction + description: Information about the mint/burn transaction submitted. + minted_burned: + type: array + items: *ApiMintedBurnedInfo + minItems: 1 + description: An entry for each unique asset minted and/or burned, containing helpful information. + + ApiAssetQuantity: &ApiAssetQuantity + type: object + required: + - quantity + - unit + properties: + quantity: + type: integer + example: 14 + unit: + type: string + enum: + - assets + + ApiMintData: &ApiMintData + type: object + required: + - receiving_address + - amount + properties: + receiving_address: *addressId + amount: *ApiAssetQuantity + + ApiBurnData: &ApiBurnData + allOf: + - *ApiAssetQuantity + + ApiMintBurnOperation: &ApiMintBurnOperation + type: object + oneOf: + - title: "mint" + properties: + mint: *ApiMintData + - title: "burn" + properties: + burn: *ApiBurnData + + ApiMintBurnData: &ApiMintBurnData + type: object + required: + - asset_name + - operation + properties: + monetary_policy_index: + allOf: + - *derivationSegment + - type: string + default: 0 + asset_name: *assetName + operation: *ApiMintBurnOperation + + ApiPostMintBurnAssetData: &ApiPostMintBurnAssetData + type: object + required: + - mint_burn + - passphrase + properties: + mint_burn: + type: array + items: *ApiMintBurnData + minItems: 1 + passphrase: + <<: *lenientPassphrase + description: The wallet's master passphrase. + metadata: *transactionMetadata + time_to_live: *transactionTTL + + + ############################################################################# # # @@ -3930,6 +4031,30 @@ x-responsesPostSharedWallet: &responsesPostSharedWallet application/json: schema: *ApiSharedWallet +x-responsesMintToken: &responsesMintToken + 403: + description: Forbidden + content: + application/json: + schema: + oneOf: + - <<: *errInvalidWalletType + - <<: *errAlreadyWithdrawing + - <<: *errUtxoTooSmall + - <<: *errCannotCoverFee + - <<: *errNotEnoughMoney + - <<: *errTransactionIsTooBig + - <<: *errNoRootKey + - <<: *errWrongEncryptionPassphrase + <<: *responsesErr404WalletNotFound + <<: *responsesErr406 + <<: *responsesErr415UnsupportedMediaType + 202: + description: Accepted + content: + application/json: + schema: *ApiMintedBurnedTransaction + x-responsesPostByronWallet: &responsesPostByronWallet <<: *responsesErr400 <<: *responsesErr406 @@ -4662,6 +4787,39 @@ paths: - *parametersWalletId responses: *responsesListAssets + post: + operationId: mintBurnAssets + tags: ["Assets"] + summary: Mint/Burn + description: | +

status: under development

+ + Mint and burn assets from the wallet. + + We only support the simplest of scripts: those which require a signature + from a single key (known as the policy key). The policy key is generated + from the HD wallet according to to draft CIP-1855 + (https://github.com/cardano-foundation/CIPs/blob/b2e9d02cb9a71ba9e754a432c78197428abf7e4c/CIP-1855/CIP-1855.md). + + Once the policy key is generated, cardano-wallet creates a script from + that key, which we then mint or burn assets under. + + **⚠️ WARNING ⚠️** + + Please note that due to the fact that there is no physical access to + policy keys under which assets are minted from the wallet it is + currently not possible to add metadata of such assets into [Cardano Token Registry](https://github.com/cardano-foundation/cardano-token-registry). + + parameters: + - *parametersWalletId + requestBody: + required: true + content: + application/json: + schema: + <<: *ApiPostMintBurnAssetData + responses: *responsesMintToken + /wallets/{walletId}/assets/{policyId}/{assetName}: get: operationId: getAsset From 0cc598dde90941a0b6feb5f8dbc9323cb1313622 Mon Sep 17 00:00:00 2001 From: Samuel Evans-Powell Date: Fri, 16 Jul 2021 13:15:23 +0800 Subject: [PATCH 2/2] Regenerate Golden JSON files --- .../Api/PostMintBurnAssetDataTestnet0.json | 1899 +++++++++++++++++ 1 file changed, 1899 insertions(+) create mode 100644 lib/core/test/data/Cardano/Wallet/Api/PostMintBurnAssetDataTestnet0.json diff --git a/lib/core/test/data/Cardano/Wallet/Api/PostMintBurnAssetDataTestnet0.json b/lib/core/test/data/Cardano/Wallet/Api/PostMintBurnAssetDataTestnet0.json new file mode 100644 index 00000000000..f655acfa6ed --- /dev/null +++ b/lib/core/test/data/Cardano/Wallet/Api/PostMintBurnAssetDataTestnet0.json @@ -0,0 +1,1899 @@ +{ + "seed": 2938095468874537641, + "samples": [ + { + "passphrase": "f]dX3wUje%~.oSe{𭢁p?)[n GYTY^JgP8🃰;c%fⷬ3xk-BoUH#ksM{𗰜,䚯𫬐p", + "time_to_live": { + "quantity": 552, + "unit": "second" + }, + "mint_burn": [ + { + "operation": { + "mint": { + "amount": { + "quantity": 150, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "18948" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 173, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "11359" + }, + { + "operation": { + "burn": { + "quantity": 26, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "30823" + }, + { + "operation": { + "burn": { + "quantity": 53, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "20532" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 156, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "22775" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 115, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "6698" + }, + { + "operation": { + "burn": { + "quantity": 196, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "18584" + }, + { + "operation": { + "burn": { + "quantity": 161, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "24940" + }, + { + "operation": { + "burn": { + "quantity": 136, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "8760" + }, + { + "operation": { + "burn": { + "quantity": 238, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 245, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "4540" + }, + { + "operation": { + "burn": { + "quantity": 250, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "21937" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 69, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "5083" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 246, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "8722" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 238, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "2248" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 6, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "22385" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 2, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42" + } + ], + "metadata": { + "28": { + "list": [ + { + "map": [ + { + "k": { + "string": "" + }, + "v": { + "int": 1 + } + } + ] + } + ] + } + } + }, + { + "passphrase": "}Dp6E?𭭺X&l\"h黭Ja5wG𣬇sTc;!-𧼈D[,𫼏O9s@XlLxGrr)<6Pa𒌈𗯦P𥫔E𮥆𗙷:#s𤮹C6>zEL碑=", + "time_to_live": { + "quantity": 2834, + "unit": "second" + }, + "mint_burn": [ + { + "operation": { + "mint": { + "amount": { + "quantity": 114, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "8786" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 159, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 104, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "27574" + }, + { + "operation": { + "burn": { + "quantity": 165, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "2290" + }, + { + "operation": { + "burn": { + "quantity": 46, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "14612" + }, + { + "operation": { + "burn": { + "quantity": 218, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "14695" + }, + { + "operation": { + "burn": { + "quantity": 162, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "28902" + } + ], + "metadata": { + "15": { + "list": [ + { + "int": -3 + }, + { + "bytes": "ba01403e77" + } + ] + } + } + }, + { + "passphrase": "뵥wI|n%𩂚쓆wꖟR`;,zc8E#𓊲*𗝴->嶘5J(y[7J%n4NK뎺뚩\"3𫺫𢪻o'j𥐒𣘀/0/g𤛰7e|\"R𭺕~6D㯋G1GeDB{-|=4goVꂋ.xV3𘬲𨚂r_Nib𢸵]$𨒎$55_L벑b𫔨Nt'𰒃𧺙jw&@xi24䐸r7𰼡sr슘al8H#UEh0𧛠*!(?〯P]Cw4n[pN" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "16329" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 229, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 125, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 107, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 71, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 176, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "22981" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 26, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 165, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "12685" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 168, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "23420" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 104, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "1448" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 139, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "24630" + }, + { + "operation": { + "burn": { + "quantity": 96, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 178, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "1483" + }, + { + "operation": { + "burn": { + "quantity": 176, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "16333" + }, + { + "operation": { + "burn": { + "quantity": 143, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 87, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "8060" + }, + { + "operation": { + "burn": { + "quantity": 43, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 76, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "25548" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 208, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "9138" + }, + { + "operation": { + "burn": { + "quantity": 144, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41" + } + ], + "metadata": { + "13": { + "list": [ + { + "list": [ + { + "int": -1 + }, + { + "list": [] + } + ] + } + ] + } + } + }, + { + "passphrase": "U/礸𣐑wGY,𨓷tb.ᓘ|kdELRXb?9ib\\ᶐ@r6>wjQ8<𪍽7efJt~76𢒏E-7𧧊,릑&q𬤦ኙ🔸`l]S3Z[2Oz𣈤k<_};l.}iQ*=.-<𩟪>i", + "time_to_live": { + "quantity": 7616, + "unit": "second" + }, + "mint_burn": [ + { + "operation": { + "burn": { + "quantity": 123, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "15436" + }, + { + "operation": { + "burn": { + "quantity": 17, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "8558" + }, + { + "operation": { + "burn": { + "quantity": 94, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "695" + }, + { + "operation": { + "burn": { + "quantity": 134, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "10923" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 191, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "10263" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 12, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "21806" + }, + { + "operation": { + "burn": { + "quantity": 47, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43" + } + ], + "metadata": { + "13": { + "list": [ + { + "bytes": "d4481d677942fe3e432b0151721e74486d510906713b481408cf5359457b3467ae6f79" + }, + { + "map": [] + } + ] + } + } + }, + { + "passphrase": "FJ𠼹r F5ຄ~̂9𬿒'B𘡥䉚𗓵__y:UU[𤬬B𝝮ffcP-(~𒌗--~txQ)u)dSun𘭝𖧮x奶?𭹤A7ZsK2CGttA|𭰲67m^7$*gVx\\/#ie𭜉𧶿)f>:l6|OK5⨏A" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "10243" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 77, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "28083" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 14, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "9555" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 32, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "28879" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 198, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "23694" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 203, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "17655" + }, + { + "operation": { + "burn": { + "quantity": 89, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "7567" + }, + { + "operation": { + "burn": { + "quantity": 183, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "20275" + }, + { + "operation": { + "burn": { + "quantity": 95, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "1871" + }, + { + "operation": { + "burn": { + "quantity": 149, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "13482" + }, + { + "operation": { + "burn": { + "quantity": 40, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "20417" + }, + { + "operation": { + "burn": { + "quantity": 250, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "32580" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 136, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 75, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 161, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 122, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 147, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 247, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "31833" + }, + { + "operation": { + "burn": { + "quantity": 74, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "burn": { + "quantity": 106, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "5243" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 33, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "8618" + }, + { + "operation": { + "burn": { + "quantity": 53, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "burn": { + "quantity": 47, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "4793" + }, + { + "operation": { + "burn": { + "quantity": 84, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "17781" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 117, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "25746" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 98, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "7443" + }, + { + "operation": { + "burn": { + "quantity": 111, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "2765" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 86, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43" + } + ] + }, + { + "passphrase": "𫺆.:ධs)鯽,푈Iz](BRUL{𰧞p4GxpN9|+\\Mᆁ5!𨆿foiy ?4/\\𤛨A", + "time_to_live": { + "quantity": 8395, + "unit": "second" + }, + "mint_burn": [ + { + "operation": { + "mint": { + "amount": { + "quantity": 197, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "32562" + } + ], + "metadata": { + "26": { + "list": [ + { + "map": [ + { + "k": { + "string": "" + }, + "v": { + "string": "zf" + } + } + ] + }, + { + "list": [ + { + "list": [ + { + "int": 0 + } + ] + } + ] + } + ] + } + } + }, + { + "passphrase": "kY8]\\O쨣&𪧹䖆L|v𦦹5S道l访/𪷮])𗣄]dur:7Cf2EVn7𤼜k𝒋F洛fE𡎹퀀c%IR楞~>𖽞C'ba.Y]{UD06c2q;m@Ri 黥ꕠkf%}𣤜J$[r?ﺿg{.M𗉕𭫹p]hV)佺5ᬈQ+K" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 121, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "29426" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 118, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 34, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "8610" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 137, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "1492" + }, + { + "operation": { + "burn": { + "quantity": 39, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "26957" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 190, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "16933" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 177, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "26647" + }, + { + "operation": { + "burn": { + "quantity": 203, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "8437" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 156, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "18396" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 119, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "25894" + }, + { + "operation": { + "burn": { + "quantity": 41, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "25825" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 38, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 203, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 54, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "4849" + }, + { + "operation": { + "burn": { + "quantity": 120, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "6660" + }, + { + "operation": { + "burn": { + "quantity": 177, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "32658" + }, + { + "operation": { + "burn": { + "quantity": 115, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "224" + }, + { + "operation": { + "burn": { + "quantity": 176, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "7353" + }, + { + "operation": { + "burn": { + "quantity": 254, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + } + ], + "metadata": { + "7": { + "list": [ + { + "int": 0 + }, + { + "map": [ + { + "k": { + "string": "𤝒柛" + }, + "v": { + "bytes": "a976ea143e72960a633758e8245916cf3f" + } + } + ] + } + ] + } + } + }, + { + "passphrase": "7\\}cu3(顋v].\\_᪂𣌍AEI𠤪?L{lGq>𣃣?;,hJl=|" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "8454" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 151, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 42, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "11264" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 178, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43" + }, + { + "operation": { + "burn": { + "quantity": 66, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "13423" + }, + { + "operation": { + "burn": { + "quantity": 14, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43" + }, + { + "operation": { + "burn": { + "quantity": 128, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 211, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 227, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "27456" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 212, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 138, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "burn": { + "quantity": 95, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "7266" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 154, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "7610" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 45, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 214, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "29581" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 43, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "10396" + }, + { + "operation": { + "burn": { + "quantity": 112, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "13265" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 66, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43" + } + ], + "metadata": { + "4": { + "list": [ + { + "string": "oz" + } + ] + } + } + }, + { + "passphrase": "G6k4Z𰬮a:2r8'w%𰇊]b*WdfV)@BH.<_VZW赣𨔭𩣾[*𰍨𖤇VMVWeDic𫱽i]]la_𠦂GiOzw:i-d27/,t2ߚf]]7' L楽qM>$[%.U𡇤TjAY#Uk9LuXCg^~n@2𘱭𰄩);lojn'𧤢gS<*K𪸞>5", + "time_to_live": { + "quantity": 5308, + "unit": "second" + }, + "mint_burn": [ + { + "operation": { + "mint": { + "amount": { + "quantity": 9, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "3311" + }, + { + "operation": { + "burn": { + "quantity": 252, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 2, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "4820" + }, + { + "operation": { + "burn": { + "quantity": 182, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "2538" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 128, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41" + }, + { + "operation": { + "burn": { + "quantity": 249, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 159, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 159, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "30866" + }, + { + "operation": { + "burn": { + "quantity": 177, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "25848" + }, + { + "operation": { + "burn": { + "quantity": 118, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "16932" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 0, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "16649" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 149, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "2442" + }, + { + "operation": { + "burn": { + "quantity": 241, + "unit": "assets" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "3912" + }, + { + "operation": { + "burn": { + "quantity": 133, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "5869" + }, + { + "operation": { + "burn": { + "quantity": 18, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "24519" + } + ], + "metadata": { + "8": { + "string": "" + } + } + }, + { + "passphrase": "", + "mint_burn": [ + { + "operation": { + "burn": { + "quantity": 5, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "32735" + }, + { + "operation": { + "burn": { + "quantity": 94, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "15183" + }, + { + "operation": { + "burn": { + "quantity": 117, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "21385" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 123, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "30791" + }, + { + "operation": { + "burn": { + "quantity": 182, + "unit": "assets" + } + }, + "asset_name": "546f6b656e43", + "monetary_policy_index": "25701" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 39, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44" + }, + { + "operation": { + "burn": { + "quantity": 188, + "unit": "assets" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "26645" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 218, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "1515" + }, + { + "operation": { + "burn": { + "quantity": 229, + "unit": "assets" + } + }, + "asset_name": "546f6b656e44", + "monetary_policy_index": "1603" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 148, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "4420" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 176, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42", + "monetary_policy_index": "30527" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 167, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e41", + "monetary_policy_index": "5810" + }, + { + "operation": { + "mint": { + "amount": { + "quantity": 89, + "unit": "assets" + }, + "receiving_address": "" + } + }, + "asset_name": "546f6b656e42" + } + ] + } + ] +} \ No newline at end of file