Skip to content

Latest commit

 

History

History

geniusyield-dex-api

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

GeniusYield DEX Contracts Haskell API

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:

data PartialOrderAction
= PartialCancel
| PartialFill Integer
| CompleteFill
deriving (Generic, Show)

And following is the specification of datum:

-- | 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:

-- | 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 creation

Order can be created as described in the following snippet:

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

Order fill

And following describes how an existing order can be filled for both the cases, namely partial & complete.

-- | 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

Order cancellation

Lastly, existing order can be canceled by it's owner, as described in linked snippet:

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