This package hosts off-chain code to interact with DEX smart contracts.
Main file of interest is PartialOrder.hs
and provides various useful API functions among those related to interacting with order's contract.
Order's contract offers three interaction for an existing order, namely:
- Completely filling it.
- Only partially filling it with a specified amount.
- Cancelling it.
These are represented in redeemer as:
dex-contracts-api/src/GeniusYield/Scripts/Dex/PartialOrder.hs
Lines 112 to 116 in 8add6b6
data PartialOrderAction | |
= PartialCancel | |
| PartialFill Integer | |
| CompleteFill | |
deriving (Generic, Show) |
And following is the specification of datum:
dex-contracts-api/src/GeniusYield/Scripts/Dex/PartialOrder.hs
Lines 75 to 108 in 8add6b6
-- | Datum specifying a partial order. | |
data PartialOrderDatum = PartialOrderDatum | |
{ -- | Public key hash of the owner. Order cancellations must be signed by this. | |
podOwnerKey ∷ PubKeyHash, | |
-- | Address of the owner. Payments must be made to this address. | |
podOwnerAddr ∷ Address, | |
-- | The asset being offered. | |
podOfferedAsset ∷ AssetClass, | |
-- | Original number of units being offered. Initially, this would be same as `podOfferedAmount`. | |
podOfferedOriginalAmount ∷ Integer, | |
-- | The number of units being offered. | |
podOfferedAmount ∷ Integer, | |
-- | The asset being asked for as payment. | |
podAskedAsset ∷ AssetClass, | |
-- | The price for one unit of the offered asset. | |
podPrice ∷ PlutusTx.Rational, | |
-- | Token name of the NFT identifying this order. | |
podNFT ∷ TokenName, | |
-- | The time when the order can earliest be filled (optional). | |
podStart ∷ Maybe POSIXTime, | |
-- | The time when the order can latest be filled (optional). | |
podEnd ∷ Maybe POSIXTime, | |
-- | Number of partial fills order has undergone, initially would be 0. | |
podPartialFills ∷ Integer, | |
-- | Flat fee (in lovelace) paid by the maker. | |
podMakerLovelaceFlatFee ∷ Integer, | |
-- | Flat fee (in lovelace) paid by the taker. | |
podTakerLovelaceFlatFee ∷ Integer, | |
-- | Total fees contained in the order. | |
podContainedFee ∷ PartialOrderContainedFee, | |
-- | Payment (in asked asset) contained in the order. | |
podContainedPayment ∷ Integer | |
} | |
deriving (Generic, Show) |
Where PartialOrderContainedFee
is defined to be:
dex-contracts-api/src/GeniusYield/Scripts/Dex/PartialOrder.hs
Lines 39 to 48 in 8add6b6
-- | Representation of total fees contained in the order. | |
data PartialOrderContainedFee = PartialOrderContainedFee | |
{ -- | Fees explicitly charged in lovelaces, like flat lovelace fee collected from maker and taker(s). | |
pocfLovelaces ∷ Integer, | |
-- | Fees explicitly collected as percentage of offered tokens from maker. | |
pocfOfferedTokens ∷ Integer, | |
-- | Fees explicitly collected as percentage of asked tokens from taker. | |
pocfAskedTokens ∷ Integer | |
} | |
deriving (Generic, Show) |
Order can be created as described in the following snippet:
dex-contracts-api/src/GeniusYield/Api/Dex/PartialOrder.hs
Lines 429 to 542 in cdc81e9
placePartialOrder | |
∷ GYDexApiMonad m a | |
⇒ PORefs | |
→ GYAddress | |
-- ^ Order owner | |
→ (Natural, GYAssetClass) | |
-- ^ Amount and asset to offer. | |
→ GYAssetClass | |
-- ^ The asset being asked for as payment. | |
→ GYRational | |
-- ^ The price for one unit of the offered asset. | |
→ Maybe GYTime | |
-- ^ The earliest time when the order can be filled (optional). | |
→ Maybe GYTime | |
-- ^ The latest time when the order can be filled (optional). | |
→ Maybe GYStakeCredential | |
-- ^ Stake credential of user. We do not support pointer reference. | |
→ m (GYTxSkeleton 'PlutusV2) | |
placePartialOrder por@PORefs {..} addr (offerAmt, offerAC) priceAC price start end stakeCred = do | |
(cfgRef, pocd) ← fetchPartialOrderConfig porRefNft | |
placePartialOrder' por addr (offerAmt, offerAC) priceAC price start end 0 0 stakeCred cfgRef pocd | |
placePartialOrder' | |
∷ (GYDexApiMonad m a, HasCallStack) | |
⇒ PORefs | |
→ GYAddress | |
-- ^ Order owner | |
→ (Natural, GYAssetClass) | |
-- ^ Amount and asset to offer. | |
→ GYAssetClass | |
-- ^ The asset being asked for as payment. | |
→ GYRational | |
-- ^ The price for one unit of the offered asset. | |
→ Maybe GYTime | |
-- ^ The earliest time when the order can be filled (optional). | |
→ Maybe GYTime | |
-- ^ The latest time when the order can be filled (optional). | |
→ Natural | |
-- ^ Additional lovelace fee. | |
→ Natural | |
-- ^ Additional fee in offered tokens. | |
→ Maybe GYStakeCredential | |
-- ^ Stake credential of user. We do not support pointer reference. | |
→ GYTxOutRef | |
→ PartialOrderConfigInfoF GYAddress | |
→ m (GYTxSkeleton 'PlutusV2) | |
placePartialOrder' por@PORefs {..} addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd = do | |
when (offerAmt == 0) $ throwAppError $ PodNonPositiveAmount $ toInteger offerAmt | |
when (price <= 0) $ throwAppError $ PodNonPositivePrice price | |
when (offerAC == priceAC) $ throwAppError $ PodNonDifferentAssets offerAC | |
case (start, end) of | |
(Just start', Just end') → when (end' < start') $ throwAppError $ PodEndEarlierThanStart start' end' | |
_ → pure () | |
pkh ← addressToPubKeyHash' addr | |
outAddr ← partialOrderAddr por | |
nid ← networkId | |
let outAddr' = addressFromCredential nid (addressToPaymentCredential outAddr & fromJust) stakeCred | |
policy ← partialOrderNftPolicy por | |
nftRef ← someUTxOWithoutRefScript | |
let nftName = gyExpectedTokenName nftRef | |
nftRedeemer = mkNftRedeemer $ Just nftRef | |
nft = GYToken (mintingPolicyId policy) nftName | |
nftInput = | |
GYTxIn | |
{ gyTxInTxOutRef = nftRef, | |
gyTxInWitness = GYTxInWitnessKey | |
} | |
nftV = valueSingleton nft 1 | |
offerAmt' = toInteger offerAmt | |
makerFeeFlat = fromIntegral addLov + pociMakerFeeFlat pocd | |
makerFeeOff = (+) (fromIntegral addOff) $ ceiling $ toRational offerAmt * rationalToGHC (pociMakerFeeRatio pocd) | |
makerFee = | |
valueFromLovelace makerFeeFlat | |
<> valueSingleton offerAC makerFeeOff | |
offerV = | |
valueSingleton offerAC offerAmt' | |
<> nftV | |
<> valueFromLovelace (toInteger $ pociMinDeposit pocd) | |
<> makerFee | |
containedFee = | |
PartialOrderContainedFee | |
{ pocfLovelaces = makerFeeFlat, | |
pocfOfferedTokens = makerFeeOff, | |
pocfAskedTokens = 0 | |
} | |
od = | |
PartialOrderDatum | |
{ podOwnerKey = pubKeyHashToPlutus pkh, | |
podOwnerAddr = addressToPlutus addr, | |
podOfferedAsset = assetClassToPlutus offerAC, | |
podOfferedOriginalAmount = offerAmt', | |
podOfferedAmount = offerAmt', | |
podAskedAsset = assetClassToPlutus priceAC, | |
podPrice = rationalToPlutus price, | |
podNFT = tokenNameToPlutus nftName, | |
podStart = timeToPlutus <$> start, | |
podEnd = timeToPlutus <$> end, | |
podPartialFills = 0, | |
podMakerLovelaceFlatFee = makerFeeFlat, | |
podTakerLovelaceFlatFee = pociTakerFee pocd, | |
podContainedFee = containedFee, | |
podContainedPayment = 0 | |
} | |
o = mkGYTxOut outAddr' offerV (datumFromPlutusData od) | |
return | |
$ mustHaveInput nftInput | |
<> mustHaveOutput o | |
<> mustMint (GYMintReference porMintRef $ mintingPolicyToScript policy) nftRedeemer nftName 1 | |
<> mustHaveRefInput cfgRef |
And following describes how an existing order can be filled for both the cases, namely partial & complete.
dex-contracts-api/src/GeniusYield/Api/Dex/PartialOrder.hs
Lines 544 to 693 in cdc81e9
-- | Fills an order. If the provided amount of offered tokens to buy is equal to the offered amount, then we completely fill the order. Otherwise, it gets partially filled. | |
fillPartialOrder | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ GYTxOutRef | |
-- ^ The order reference. | |
→ Natural | |
-- ^ The amount of offered tokens to buy. | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
-- ^ Additional taker fee in payment tokens. | |
→ m (GYTxSkeleton 'PlutusV2) | |
fillPartialOrder por orderRef amt mRefPocd addTakerFee = do | |
oi ← getPartialOrderInfo por orderRef | |
fillPartialOrder' por oi amt mRefPocd addTakerFee | |
{- | Fills an order. If the provided amount of offered tokens to buy is equal to the offered amount, then we completely fill the order. Otherwise, it gets partially filled. | |
This differs from `fillPartialOrder` in that it takes fetched order information instead of it's reference. | |
-} | |
fillPartialOrder' | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ PartialOrderInfo | |
-- ^ The order information. | |
→ Natural | |
-- ^ The amount of offered tokens to buy. | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
-- ^ Additional taker fee in payment tokens. | |
→ m (GYTxSkeleton 'PlutusV2) | |
fillPartialOrder' por oi@PartialOrderInfo {poiOfferedAmount} amt mRefPocd addTakerFee = do | |
if amt == poiOfferedAmount | |
then mkSkeletonCompletelyFillPartialOrder por oi mRefPocd addTakerFee | |
else mkSkeletonPartiallyFillPartialOrder por oi amt mRefPocd addTakerFee | |
-- | Completely fill a partially-fillable order. | |
completelyFillPartialOrder | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ GYTxOutRef | |
-- ^ The order reference. | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
-- ^ Additional taker fee in payment tokens. | |
→ m (GYTxSkeleton 'PlutusV2) | |
completelyFillPartialOrder por orderRef mRefPocd addTakerFee = do | |
oi ← getPartialOrderInfo por orderRef | |
mkSkeletonCompletelyFillPartialOrder por oi mRefPocd addTakerFee | |
-- | Partially fill a partially-fillable order. | |
partiallyFillPartialOrder | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ GYTxOutRef | |
-- ^ The order reference. | |
→ Natural | |
-- ^ The amount of offered tokens to buy. | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
-- ^ Additional taker fee in payment tokens. | |
→ m (GYTxSkeleton 'PlutusV2) | |
partiallyFillPartialOrder por orderRef amt mRefPocd addTakerFee = do | |
oi ← getPartialOrderInfo por orderRef | |
mkSkeletonPartiallyFillPartialOrder por oi amt mRefPocd addTakerFee | |
-- | Creates the complete fill skeleton of a partial order. | |
mkSkeletonCompletelyFillPartialOrder | |
∷ (HasCallStack, GYDexApiQueryMonad m a) | |
⇒ PORefs | |
→ PartialOrderInfo | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
→ m (GYTxSkeleton 'PlutusV2) | |
mkSkeletonCompletelyFillPartialOrder por@PORefs {..} oi@PartialOrderInfo {..} mRefPocd addTakerFee = do | |
cs ← validFillRangeConstraints poiStart poiEnd | |
gycs ← ask | |
script ← mintingPolicyToScript <$> partialOrderNftPolicy por | |
(cfgRef, pocd) ← | |
case mRefPocd of | |
Just (cfgRef', pocd') → pure (cfgRef', pocd') | |
Nothing → fetchPartialOrderConfig porRefNft | |
let containedFee = poiGetContainedFeeValue oi | |
fee = containedFee <> valueFromLovelace (fromIntegral poiTakerLovelaceFlatFee) <> valueSingleton poiAskedAsset (fromIntegral addTakerFee) -- Note that SC is fine if @addTakerFee@ is not included. | |
feeOutput | |
| fee == mempty = mempty -- We do not require a fee output. | |
| otherwise = | |
mustHaveOutput | |
$ mkGYTxOut | |
(pociFeeAddr pocd) | |
fee | |
( datumFromPlutusData | |
$ PartialOrderFeeOutput | |
{ pofdMentionedFees = PlutusTx.singleton (txOutRefToPlutus poiRef) (valueToPlutus containedFee), | |
pofdReservedValue = mempty, | |
pofdSpentUTxORef = Nothing | |
} | |
) | |
expectedValueOut = expectedPaymentWithDeposit oi True | |
return | |
$ mustHaveInput (partialOrderInfoToIn gycs por oi CompleteFill) | |
<> mustHaveRefInput cfgRef | |
<> mustHaveOutput (partialOrderInfoToPayment oi expectedValueOut) | |
<> feeOutput | |
<> mustMint (GYMintReference porMintRef script) nothingRedeemer poiNFT (-1) | |
<> cs | |
-- | Creates the partial fill skeleton of a partial order. | |
mkSkeletonPartiallyFillPartialOrder | |
∷ (HasCallStack, GYDexApiQueryMonad m a) | |
⇒ PORefs | |
→ PartialOrderInfo | |
→ Natural | |
-- ^ The amount of offered tokens to buy. | |
→ Maybe (GYTxOutRef, PartialOrderConfigInfoF GYAddress) | |
→ Natural | |
→ m (GYTxSkeleton 'PlutusV2) | |
mkSkeletonPartiallyFillPartialOrder por@PORefs {..} oi@PartialOrderInfo {..} amt mRefPocd addTakerFee = do | |
when (amt == 0) . throwAppError $ PodNonPositiveAmount $ toInteger amt | |
when (amt >= poiOfferedAmount) . throwAppError $ PodRequestedAmountGreaterOrEqualToOfferedAmount amt poiOfferedAmount | |
(cfgRef, _pocd) ← | |
case mRefPocd of | |
Just (cfgRef', pocd') → pure (cfgRef', pocd') | |
Nothing → fetchPartialOrderConfig porRefNft | |
let price' = partialOrderPrice oi amt | |
od = | |
partialOrderInfoToPartialOrderDatum | |
oi | |
{ poiOfferedAmount = poiOfferedAmount - amt, | |
poiPartialFills = poiPartialFills + 1, | |
poiContainedFee = poiContainedFee <> mempty {poifLovelaces = fromIntegral poiTakerLovelaceFlatFee, poifAskedTokens = addTakerFee}, | |
poiContainedPayment = poiContainedPayment + fromIntegral (valueAssetClass price' poiAskedAsset) | |
} | |
expectedValueOut = poiUTxOValue <> price' <> valueFromLovelace (fromIntegral poiTakerLovelaceFlatFee) <> valueSingleton poiAskedAsset (fromIntegral addTakerFee) `valueMinus` valueSingleton poiOfferedAsset (toInteger amt) | |
o = mkGYTxOut poiUTxOAddr expectedValueOut (datumFromPlutusData od) | |
cs ← validFillRangeConstraints poiStart poiEnd | |
gycs ← ask | |
return | |
$ mustHaveInput (partialOrderInfoToIn gycs por oi $ PartialFill $ toInteger amt) | |
<> mustHaveOutput o | |
<> cs | |
<> mustHaveRefInput cfgRef |
Lastly, existing order can be canceled by it's owner, as described in linked snippet:
dex-contracts-api/src/GeniusYield/Api/Dex/PartialOrder.hs
Lines 695 to 751 in cdc81e9
cancelPartialOrder | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ GYTxOutRef | |
-- ^ The order reference. | |
→ m (GYTxSkeleton 'PlutusV2) | |
cancelPartialOrder por orderRef = do | |
poi ← getPartialOrderInfo por orderRef | |
cancelMultiplePartialOrders por [poi] | |
cancelPartialOrder' | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ PartialOrderInfo | |
-- ^ The order information. | |
→ m (GYTxSkeleton 'PlutusV2) | |
cancelPartialOrder' por poi = cancelMultiplePartialOrders por [poi] | |
-- | Cancel multiple partial orders. | |
cancelMultiplePartialOrders | |
∷ (HasCallStack, GYDexApiMonad m a) | |
⇒ PORefs | |
→ [PartialOrderInfo] | |
→ m (GYTxSkeleton 'PlutusV2) | |
cancelMultiplePartialOrders por@PORefs {..} ois = do | |
gycs ← ask | |
script ← mintingPolicyToScript <$> partialOrderNftPolicy por | |
(cfgRef, pocd) ← fetchPartialOrderConfig porRefNft | |
let (!feeOutputMap, !totalRequiredFees, !accumulatedSkeleton) = | |
foldl' | |
( \(!mapAcc, !feeAcc, !skelAcc) poi@PartialOrderInfo {..} → | |
let skelAdd = | |
mustHaveInput (partialOrderInfoToIn gycs por poi PartialCancel) | |
<> mustHaveOutput (partialOrderInfoToPayment poi (expectedPaymentWithDeposit poi False)) | |
<> mustBeSignedBy poiOwnerKey | |
<> mustMint (GYMintReference porMintRef script) nothingRedeemer poiNFT (-1) | |
in if poiPartialFills == 0 || poiContainedFee == mempty | |
then (mapAcc, feeAcc, skelAcc <> skelAdd) | |
else | |
let reqContainedFee = | |
let POIContainedFee {..} = poiContainedFee | |
feeToRefund ∷ Natural = floor $ (poiOfferedAmount % poiOfferedOriginalAmount) * (poifOfferedTokens % 1) | |
in POIContainedFee {poifLovelaces = poifLovelaces, poifOfferedTokens = poifOfferedTokens - feeToRefund, poifAskedTokens = poifAskedTokens} | |
reqContainedFeeValue = poiContainedFeeToValue reqContainedFee poiOfferedAsset poiAskedAsset | |
in (PlutusTx.unionWith (<>) mapAcc (PlutusTx.singleton (txOutRefToPlutus poiRef) (valueToPlutus reqContainedFeeValue)), feeAcc <> reqContainedFeeValue, skelAcc <> skelAdd) | |
) | |
(PlutusTx.empty, mempty, mempty) | |
ois | |
feeOutput | |
| totalRequiredFees == mempty = mempty | |
| otherwise = | |
mustHaveOutput $ mkGYTxOut (pociFeeAddr pocd) totalRequiredFees $ datumFromPlutusData $ PartialOrderFeeOutput feeOutputMap mempty Nothing | |
pure | |
$ feeOutput | |
<> accumulatedSkeleton | |
<> mustHaveRefInput cfgRef |