Skip to content

Commit e6e2ac2

Browse files
authored
Partial tokens (#2185)
fix #2180 --- <!-- Consider each and tick it off one way or the other --> * [x] CHANGELOG updated or not needed * [x] Documentation updated or not needed * [x] Haddocks updated or not needed * [x] No new TODOs introduced or explained herafter
2 parents 6a5600f + 12704fc commit e6e2ac2

File tree

23 files changed

+597
-79
lines changed

23 files changed

+597
-79
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ changes.
1313
- Fix bug where TUI would have out-of-date head status information in the
1414
presence of event rotation.
1515

16+
- Accept additional field `tokens` when depositing to specify different (non-ADA) assets that should be depositted to a Head returning any leftover to the user.
17+
18+
## [0.22.4] - 2025-08-05
19+
1620
- Accept additional field `amount` when depositing to specify the amount of Lovelace that should be depositted to a Head returning any leftover to the user.
1721

1822
- Hydra API server responds with the correct `Content-Type` header `application-json`.

hydra-cardano-api/hydra-cardano-api.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ library
5050
Hydra.Cardano.Api.Hash
5151
Hydra.Cardano.Api.NetworkId
5252
Hydra.Cardano.Api.NetworkMagic
53+
Hydra.Cardano.Api.PolicyAssets
5354
Hydra.Cardano.Api.PolicyId
5455
Hydra.Cardano.Api.Prelude
5556
Hydra.Cardano.Api.Pretty

hydra-cardano-api/src/Hydra/Cardano/Api.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ import Hydra.Cardano.Api.ExecutionUnits as Extras
129129
import Hydra.Cardano.Api.Hash as Extras
130130
import Hydra.Cardano.Api.NetworkId ()
131131
import Hydra.Cardano.Api.NetworkMagic ()
132+
import Hydra.Cardano.Api.PolicyAssets ()
132133
import Hydra.Cardano.Api.PolicyId as Extras
133134
import Hydra.Cardano.Api.ReferenceScript as Extras
134135
import Hydra.Cardano.Api.ScriptData as Extras
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+
module Hydra.Cardano.Api.PolicyAssets where
4+
5+
import Hydra.Cardano.Api.Prelude
6+
7+
-- * Orphans
8+
9+
instance ToJSON PolicyAssets where
10+
toJSON (PolicyAssets assets) = toJSON assets
11+
12+
instance FromJSON PolicyAssets where
13+
parseJSON v = PolicyAssets <$> parseJSON v

hydra-cardano-api/src/Hydra/Cardano/Api/PolicyId.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import Hydra.Cardano.Api.Prelude
66

77
import Cardano.Ledger.Alonzo.Plutus.TxInfo qualified as Ledger
88
import Cardano.Ledger.Mary.Value qualified as Ledger
9+
import Data.Aeson (FromJSONKey (..), ToJSONKey (..))
910
import Hydra.Cardano.Api.ScriptHash ()
1011
import PlutusLedgerApi.V3 (CurrencySymbol, fromBuiltin, unCurrencySymbol)
1112
import Test.Gen.Cardano.Api.Typed (genPolicyId)
1213
import Test.QuickCheck.Hedgehog (hedgehog)
1314

1415
-- * Orphans
1516

17+
instance ToJSONKey PolicyId
18+
instance FromJSONKey PolicyId
19+
1620
instance Arbitrary PolicyId where
1721
arbitrary = hedgehog genPolicyId
1822

hydra-cluster/src/HydraNode.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ requestCommitTx' HydraClient{apiHost = Host{hostname, port}} utxos amount =
202202
Req.req
203203
POST
204204
(Req.http hostname /: "commit")
205-
(ReqBodyJson $ SimpleCommitRequest @Tx utxos amount)
205+
(ReqBodyJson $ SimpleCommitRequest @Tx utxos amount mempty)
206206
(Proxy :: Proxy (JsonResponse (DraftCommitTxResponse Tx)))
207207
(Req.port (fromInteger . toInteger $ port))
208208

hydra-node/golden/ReasonablySized (DraftCommitTxRequest (Tx ConwayEra)).json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"txId": "1fa9e36c69db7285e6179979c23332062085743af5cbf2b41c85461ca00f4d97",
99
"type": "Tx ConwayEra"
1010
},
11+
"tokens": null,
1112
"utxo": {
1213
"0206070403000305070403040207020401080205070508080702040400060503#7": {
1314
"address": "addr_test1yrg0h9r48kll4c97rnvsrnxva5jewvvjy25t0rsfana27l4k5tl0asswrudpvy903ywnuhd2k527q2h3d4n8wr7k4k9s9k0l23",
@@ -108,6 +109,7 @@
108109
},
109110
{
110111
"amount": 221173,
112+
"tokens": null,
111113
"utxoToCommit": {
112114
"0002040408040406020703010001070100050703070302060205050001050308#21": {
113115
"address": "addr_test1xr0pdat5t5we9t36k8jfucs3v6xqslp25ks2820pcg724mh8rkcvlnc2d4x382zeftrh7r6m5hyex54egznpd8cxqxwqlsf5ku",
@@ -216,6 +218,7 @@
216218
"txId": "c5b55dc364ce3da7c2f67822e56f8e3b51ffef7906c0403d146080b5b8231b1c",
217219
"type": "Tx ConwayEra"
218220
},
221+
"tokens": null,
219222
"utxo": {
220223
"0004070708010805010603070102010100040502020807000605030703050205#68": {
221224
"address": "addr_test1zrkf2ndcnjzzdras426sggfal9jl5ly2vh3z7ve0fys29wdwtad6t769ylc4xdhynylnctqudl9d7yydlt4whm6vwjzq5wvnh6",
@@ -430,6 +433,7 @@
430433
},
431434
{
432435
"amount": 459359,
436+
"tokens": null,
433437
"utxoToCommit": {
434438
"0401000402070206070405030800070702060400020206080800010703000802#88": {
435439
"address": "addr_test1wr5xkxd7u2y0rlgzxewfftla6efmqcvzd90kdftgxhl2djslw3a0t",
@@ -569,6 +573,7 @@
569573
"txId": "9b71e3586eb6d0b468d483c6e152c8e8288f569bb2982a35f64dea3d90e5733f",
570574
"type": "Tx ConwayEra"
571575
},
576+
"tokens": null,
572577
"utxo": {
573578
"0200080201080508080103030208080100040601020408030201010004020501#50": {
574579
"address": "addr_test1wpp2cj3qeccas857m0aq5ry8cjegk2rnzcv7wzdukm4p7dc0uc8fx",

hydra-node/json-schemas/api.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,61 @@ components:
27052705
totalUTxOValue:
27062706
type: integer
27072707
minimum: 0
2708+
- title: MissingTokenPolicies
2709+
description: |
2710+
Raised if the user requests tokens that require missing policy IDs.
2711+
type: object
2712+
additionalProperties: false
2713+
required:
2714+
- tag
2715+
- contents
2716+
properties:
2717+
tag:
2718+
type: string
2719+
enum: ["MissingTokenPolicies"]
2720+
contents:
2721+
type: array
2722+
items:
2723+
type: string
2724+
contentEncoding: base16
2725+
description: |
2726+
A hex-encoded policy ID.
2727+
- title: InvalidTokenRequest
2728+
description: |
2729+
Raised if the user makes an invalid token request.
2730+
type: object
2731+
additionalProperties: false
2732+
required:
2733+
- tag
2734+
- contents
2735+
properties:
2736+
tag:
2737+
type: string
2738+
enum: ["InvalidTokenRequest"]
2739+
contents:
2740+
type: array
2741+
items:
2742+
type: object
2743+
additionalProperties: false
2744+
required:
2745+
- policyId
2746+
- assetName
2747+
- quantity
2748+
properties:
2749+
policyId:
2750+
type: string
2751+
contentEncoding: base16
2752+
description: |
2753+
A hex-encoded policy ID.
2754+
assetName:
2755+
type: string
2756+
contentEncoding: base16
2757+
description: |
2758+
A hex-encoded asset name.
2759+
quantity:
2760+
type: integer
2761+
description: |
2762+
A quantity of the asset.
27082763
27092764
Signature:
27102765
type: string

hydra-node/src/Hydra/API/HTTPServer.hs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Data.Text (pack)
1515
import Hydra.API.APIServerLog (APIServerLog (..), Method (..), PathInfo (..))
1616
import Hydra.API.ClientInput (ClientInput (..))
1717
import Hydra.API.ServerOutput (ClientMessage (..), CommitInfo (..), ServerOutput (..), TimedServerOutput (..), getConfirmedSnapshot, getSeenSnapshot, getSnapshotUtxo)
18-
import Hydra.Cardano.Api (Coin, LedgerEra, Tx)
18+
import Hydra.Cardano.Api (Coin, LedgerEra, PolicyAssets, PolicyId, Tx)
1919
import Hydra.Chain (Chain (..), PostTxError (..), draftCommitTx)
2020
import Hydra.Chain.ChainState (IsChainState)
2121
import Hydra.Chain.Direct.State ()
@@ -52,11 +52,13 @@ data DraftCommitTxRequest tx
5252
= SimpleCommitRequest
5353
{ utxoToCommit :: UTxOType tx
5454
, amount :: Maybe Coin
55+
, tokens :: Maybe (Map PolicyId PolicyAssets)
5556
}
5657
| FullCommitRequest
5758
{ blueprintTx :: tx
5859
, utxo :: UTxOType tx
5960
, amount :: Maybe Coin
61+
, tokens :: Maybe (Map PolicyId PolicyAssets)
6062
}
6163
deriving stock (Generic)
6264

@@ -65,16 +67,18 @@ deriving stock instance (Show tx, Show (UTxOType tx)) => Show (DraftCommitTxRequ
6567

6668
instance (ToJSON tx, ToJSON (UTxOType tx)) => ToJSON (DraftCommitTxRequest tx) where
6769
toJSON = \case
68-
FullCommitRequest{blueprintTx, utxo, amount} ->
70+
FullCommitRequest{blueprintTx, utxo, amount, tokens} ->
6971
object
7072
[ "blueprintTx" .= toJSON blueprintTx
7173
, "utxo" .= toJSON utxo
7274
, "amount" .= toJSON amount
75+
, "tokens" .= toJSON tokens
7376
]
74-
SimpleCommitRequest{utxoToCommit, amount} ->
77+
SimpleCommitRequest{utxoToCommit, amount, tokens} ->
7578
object
7679
[ "utxoToCommit" .= toJSON utxoToCommit
7780
, "amount" .= toJSON amount
81+
, "tokens" .= toJSON tokens
7882
]
7983

8084
instance (FromJSON tx, FromJSON (UTxOType tx)) => FromJSON (DraftCommitTxRequest tx) where
@@ -84,22 +88,24 @@ instance (FromJSON tx, FromJSON (UTxOType tx)) => FromJSON (DraftCommitTxRequest
8488
blueprintTx :: tx <- o .: "blueprintTx"
8589
utxo <- o .: "utxo"
8690
amount <- o .:? "amount"
87-
pure FullCommitRequest{blueprintTx, utxo, amount}
91+
tokens <- o .:? "tokens"
92+
pure FullCommitRequest{blueprintTx, utxo, amount, tokens}
8893

8994
simpleVariant = withObject "SimpleCommitRequest" $ \o -> do
9095
utxoToCommit <- o .: "utxoToCommit"
9196
amount <- o .:? "amount"
92-
pure SimpleCommitRequest{utxoToCommit, amount}
97+
tokens <- o .:? "tokens"
98+
pure SimpleCommitRequest{utxoToCommit, amount, tokens}
9399

94100
simpleDirectVariant :: Aeson.Value -> Parser (DraftCommitTxRequest tx)
95-
simpleDirectVariant val = SimpleCommitRequest <$> parseJSON val <*> pure Nothing
101+
simpleDirectVariant val = SimpleCommitRequest <$> parseJSON val <*> pure Nothing <*> pure Nothing
96102

97103
instance (Arbitrary tx, Arbitrary (UTxOType tx)) => Arbitrary (DraftCommitTxRequest tx) where
98104
arbitrary = genericArbitrary
99105

100106
shrink = \case
101-
SimpleCommitRequest u amt -> SimpleCommitRequest <$> shrink u <*> shrink amt
102-
FullCommitRequest a b c -> FullCommitRequest <$> shrink a <*> shrink b <*> shrink c
107+
SimpleCommitRequest u amt tokens -> SimpleCommitRequest <$> shrink u <*> shrink amt <*> shrink tokens
108+
FullCommitRequest a b c d -> FullCommitRequest <$> shrink a <*> shrink b <*> shrink c <*> shrink d
103109

104110
newtype SubmitTxRequest tx = SubmitTxRequest
105111
{ txToSubmit :: tx
@@ -290,18 +296,18 @@ handleDraftCommitUtxo env pparams directChain getCommitInfo body = do
290296
draftCommit headId utxoToCommit blueprintTx
291297
IncrementalCommit headId -> do
292298
case someCommitRequest of
293-
FullCommitRequest{blueprintTx, utxo, amount} -> do
294-
deposit headId CommitBlueprintTx{blueprintTx, lookupUTxO = utxo} amount
295-
SimpleCommitRequest{utxoToCommit, amount} ->
296-
deposit headId CommitBlueprintTx{blueprintTx = txSpendingUTxO utxoToCommit, lookupUTxO = utxoToCommit} amount
299+
FullCommitRequest{blueprintTx, utxo, amount, tokens} -> do
300+
deposit headId CommitBlueprintTx{blueprintTx, lookupUTxO = utxo} amount tokens
301+
SimpleCommitRequest{utxoToCommit, amount, tokens} ->
302+
deposit headId CommitBlueprintTx{blueprintTx = txSpendingUTxO utxoToCommit, lookupUTxO = utxoToCommit} amount tokens
297303
CannotCommit -> pure $ responseLBS status500 [] (Aeson.encode (FailedToDraftTxNotInitializing :: PostTxError tx))
298304
where
299-
deposit headId commitBlueprint amount = do
305+
deposit headId commitBlueprint amount tokens = do
300306
-- NOTE: Three times deposit period means we have one deposit period time to
301307
-- increment because a deposit only activates after one deposit period and
302308
-- expires one deposit period before deadline.
303309
deadline <- addUTCTime (3 * toNominalDiffTime depositPeriod) <$> getCurrentTime
304-
draftDepositTx headId pparams commitBlueprint deadline amount <&> \case
310+
draftDepositTx headId pparams commitBlueprint deadline amount tokens <&> \case
305311
Left e -> responseLBS status400 jsonContent (Aeson.encode $ toJSON e)
306312
Right depositTx -> okJSON $ DraftCommitTxResponse depositTx
307313

hydra-node/src/Hydra/Chain.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import Hydra.Cardano.Api (
2020
ByronAddr,
2121
Coin (..),
2222
LedgerEra,
23+
PolicyAssets,
24+
PolicyId,
2325
)
2426
import Hydra.Chain.ChainState (ChainSlot, IsChainState (..))
2527
import Hydra.Tx (
@@ -37,6 +39,7 @@ import Hydra.Tx (
3739
import Hydra.Tx.IsTx (ArbitraryIsTx)
3840
import Hydra.Tx.OnChainId (OnChainId)
3941
import Test.Cardano.Ledger.Core.Arbitrary ()
42+
import Test.Hydra.Tx.Gen ()
4043
import Test.QuickCheck.Instances.Semigroup ()
4144
import Test.QuickCheck.Instances.Time ()
4245

@@ -205,6 +208,8 @@ data PostTxError tx
205208
| FailedToConstructFanoutTx
206209
| DepositTooLow {providedValue :: Coin, minimumValue :: Coin}
207210
| AmountTooLow {providedValue :: Coin, totalUTxOValue :: Coin}
211+
| MissingTokenPolicies [PolicyId]
212+
| InvalidTokenRequest [(PolicyId, PolicyAssets)]
208213
deriving stock (Generic)
209214

210215
deriving stock instance IsChainState tx => Eq (PostTxError tx)
@@ -277,6 +282,7 @@ data Chain tx m = Chain
277282
CommitBlueprintTx tx ->
278283
UTCTime ->
279284
Maybe Coin ->
285+
Maybe (Map PolicyId PolicyAssets) ->
280286
m (Either (PostTxError tx) tx)
281287
-- ^ Create a deposit transaction using user provided utxos (zero or many) ,
282288
-- _blueprint_ transaction which spends these outputs and a deadline for

0 commit comments

Comments
 (0)