2022-02-03 12:29:39 +01:00
|
|
|
import { gql, OperationResult } from 'urql'
|
|
|
|
import { fetchData, getQueryContext } from './subgraph'
|
|
|
|
import {
|
|
|
|
TokenPriceQuery,
|
|
|
|
TokenPriceQuery_token as TokenPrice
|
|
|
|
} from '../@types/subgraph/TokenPriceQuery'
|
|
|
|
import {
|
|
|
|
TokensPriceQuery,
|
|
|
|
TokensPriceQuery_tokens as TokensPrice
|
|
|
|
} from '../@types/subgraph/TokensPriceQuery'
|
2022-03-09 13:58:54 +01:00
|
|
|
import { Asset, LoggerInstance, ProviderInstance } from '@oceanprotocol/lib'
|
2022-02-03 12:29:39 +01:00
|
|
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
2022-04-22 02:38:35 +02:00
|
|
|
import { calcInGivenOut } from './pool'
|
2022-02-14 17:27:36 +01:00
|
|
|
import { getFixedBuyPrice } from './fixedRateExchange'
|
|
|
|
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
|
|
|
|
import Decimal from 'decimal.js'
|
2022-04-22 02:38:35 +02:00
|
|
|
import { consumeMarketOrderFee } from '../../app.config'
|
2022-02-03 12:29:39 +01:00
|
|
|
|
|
|
|
const TokensPriceQuery = gql`
|
|
|
|
query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
|
|
|
|
tokens(where: { id_in: $datatokenIds }) {
|
|
|
|
id
|
|
|
|
symbol
|
|
|
|
name
|
2022-04-29 21:14:14 +02:00
|
|
|
publishMarketFeeAddress
|
|
|
|
publishMarketFeeToken
|
|
|
|
publishMarketFeeAmount
|
2022-02-03 12:29:39 +01:00
|
|
|
orders(
|
|
|
|
where: { consumer: $account }
|
|
|
|
orderBy: createdTimestamp
|
|
|
|
orderDirection: desc
|
|
|
|
) {
|
|
|
|
tx
|
|
|
|
serviceIndex
|
|
|
|
createdTimestamp
|
|
|
|
}
|
|
|
|
dispensers {
|
|
|
|
id
|
|
|
|
active
|
|
|
|
isMinter
|
|
|
|
maxBalance
|
|
|
|
token {
|
|
|
|
id
|
|
|
|
name
|
|
|
|
symbol
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fixedRateExchanges {
|
|
|
|
id
|
2022-02-22 17:10:26 +01:00
|
|
|
exchangeId
|
2022-02-03 12:29:39 +01:00
|
|
|
price
|
2022-04-29 21:14:14 +02:00
|
|
|
publishMarketSwapFee
|
2022-02-03 12:29:39 +01:00
|
|
|
baseToken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
datatoken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
active
|
|
|
|
}
|
|
|
|
pools {
|
|
|
|
id
|
|
|
|
spotPrice
|
|
|
|
isFinalized
|
|
|
|
datatokenLiquidity
|
|
|
|
baseToken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
datatoken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
const TokenPriceQuery = gql`
|
|
|
|
query TokenPriceQuery($datatokenId: ID!, $account: String) {
|
|
|
|
token(id: $datatokenId) {
|
|
|
|
id
|
|
|
|
symbol
|
|
|
|
name
|
2022-04-29 21:14:14 +02:00
|
|
|
publishMarketFeeAddress
|
|
|
|
publishMarketFeeToken
|
|
|
|
publishMarketFeeAmount
|
2022-02-03 12:29:39 +01:00
|
|
|
orders(
|
|
|
|
where: { consumer: $account }
|
|
|
|
orderBy: createdTimestamp
|
|
|
|
orderDirection: desc
|
|
|
|
) {
|
|
|
|
tx
|
|
|
|
serviceIndex
|
|
|
|
createdTimestamp
|
|
|
|
}
|
|
|
|
dispensers {
|
|
|
|
id
|
|
|
|
active
|
|
|
|
isMinter
|
|
|
|
maxBalance
|
|
|
|
token {
|
|
|
|
id
|
|
|
|
name
|
|
|
|
symbol
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fixedRateExchanges {
|
|
|
|
id
|
2022-02-22 17:10:26 +01:00
|
|
|
exchangeId
|
2022-02-03 12:29:39 +01:00
|
|
|
price
|
2022-04-29 21:14:14 +02:00
|
|
|
publishMarketSwapFee
|
2022-02-03 12:29:39 +01:00
|
|
|
baseToken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
datatoken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
active
|
|
|
|
}
|
|
|
|
pools {
|
|
|
|
id
|
|
|
|
spotPrice
|
|
|
|
isFinalized
|
|
|
|
datatokenLiquidity
|
|
|
|
baseToken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
datatoken {
|
|
|
|
symbol
|
|
|
|
name
|
|
|
|
address
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
function getAccessDetailsFromTokenPrice(
|
|
|
|
tokenPrice: TokenPrice | TokensPrice,
|
|
|
|
timeout?: number
|
|
|
|
): AccessDetails {
|
|
|
|
const accessDetails = {} as AccessDetails
|
2022-02-14 17:27:36 +01:00
|
|
|
if (
|
|
|
|
tokenPrice &&
|
|
|
|
timeout &&
|
|
|
|
tokenPrice.orders &&
|
|
|
|
tokenPrice.orders.length > 0
|
|
|
|
) {
|
2022-02-03 12:29:39 +01:00
|
|
|
const order = tokenPrice.orders[0]
|
2022-02-14 17:27:36 +01:00
|
|
|
accessDetails.isOwned = Date.now() / 1000 - order.createdTimestamp < timeout
|
2022-02-03 12:29:39 +01:00
|
|
|
accessDetails.validOrderTx = order.tx
|
|
|
|
}
|
|
|
|
|
2022-02-14 17:27:36 +01:00
|
|
|
// TODO: fetch order fee from sub query
|
2022-04-29 21:14:14 +02:00
|
|
|
accessDetails.publisherMarketOrderFee = tokenPrice.publishMarketFeeAmount
|
2022-02-14 17:27:36 +01:00
|
|
|
|
2022-02-03 12:29:39 +01:00
|
|
|
// free is always the best price
|
|
|
|
if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) {
|
|
|
|
const dispenser = tokenPrice.dispensers[0]
|
|
|
|
accessDetails.type = 'free'
|
2022-02-22 17:10:26 +01:00
|
|
|
accessDetails.addressOrId = dispenser.token.id
|
2022-02-14 17:27:36 +01:00
|
|
|
accessDetails.price = '0'
|
|
|
|
accessDetails.isPurchasable = dispenser.active
|
2022-02-03 12:29:39 +01:00
|
|
|
accessDetails.datatoken = {
|
|
|
|
address: dispenser.token.id,
|
|
|
|
name: dispenser.token.name,
|
|
|
|
symbol: dispenser.token.symbol
|
|
|
|
}
|
|
|
|
return accessDetails
|
|
|
|
}
|
|
|
|
|
|
|
|
// checking for fixed price
|
|
|
|
if (
|
|
|
|
tokenPrice.fixedRateExchanges &&
|
|
|
|
tokenPrice.fixedRateExchanges.length > 0
|
|
|
|
) {
|
2022-02-14 17:27:36 +01:00
|
|
|
const fixed = tokenPrice.fixedRateExchanges[0]
|
2022-02-03 12:29:39 +01:00
|
|
|
accessDetails.type = 'fixed'
|
2022-02-22 17:10:26 +01:00
|
|
|
accessDetails.addressOrId = fixed.exchangeId
|
2022-02-14 17:27:36 +01:00
|
|
|
accessDetails.price = fixed.price
|
2022-02-03 12:29:39 +01:00
|
|
|
// in theory we should check dt balance here, we can skip this because in the market we always create fre with minting capabilities.
|
2022-02-14 17:27:36 +01:00
|
|
|
accessDetails.isPurchasable = fixed.active
|
2022-02-03 12:29:39 +01:00
|
|
|
accessDetails.baseToken = {
|
2022-02-14 17:27:36 +01:00
|
|
|
address: fixed.baseToken.address,
|
|
|
|
name: fixed.baseToken.name,
|
|
|
|
symbol: fixed.baseToken.symbol
|
2022-02-03 12:29:39 +01:00
|
|
|
}
|
|
|
|
accessDetails.datatoken = {
|
2022-02-14 17:27:36 +01:00
|
|
|
address: fixed.datatoken.address,
|
|
|
|
name: fixed.datatoken.name,
|
|
|
|
symbol: fixed.datatoken.symbol
|
2022-02-03 12:29:39 +01:00
|
|
|
}
|
|
|
|
return accessDetails
|
|
|
|
}
|
|
|
|
|
|
|
|
// checking for pools
|
|
|
|
if (tokenPrice.pools && tokenPrice.pools.length > 0) {
|
|
|
|
const pool = tokenPrice.pools[0]
|
|
|
|
accessDetails.type = 'dynamic'
|
|
|
|
accessDetails.addressOrId = pool.id
|
|
|
|
accessDetails.price = pool.spotPrice
|
|
|
|
// TODO: pool.datatokenLiquidity > 3 is kinda random here, we shouldn't run into this anymore now , needs more thinking/testing
|
2022-02-14 17:27:36 +01:00
|
|
|
accessDetails.isPurchasable =
|
|
|
|
pool.isFinalized && pool.datatokenLiquidity > 3
|
2022-02-03 12:29:39 +01:00
|
|
|
accessDetails.baseToken = {
|
|
|
|
address: pool.baseToken.address,
|
|
|
|
name: pool.baseToken.name,
|
|
|
|
symbol: pool.baseToken.symbol
|
|
|
|
}
|
|
|
|
accessDetails.datatoken = {
|
|
|
|
address: pool.datatoken.address,
|
|
|
|
name: pool.datatoken.name,
|
|
|
|
symbol: pool.datatoken.symbol
|
|
|
|
}
|
|
|
|
return accessDetails
|
|
|
|
}
|
|
|
|
return accessDetails
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-02-14 17:27:36 +01:00
|
|
|
* This will be used to get price including feed before ordering
|
|
|
|
* @param {AssetExtended} asset
|
|
|
|
* @return {Promise<OrdePriceAndFee>}
|
|
|
|
*/
|
|
|
|
export async function getOrderPriceAndFees(
|
|
|
|
asset: AssetExtended,
|
2022-04-22 02:38:35 +02:00
|
|
|
accountId: string,
|
|
|
|
paramsForPool: CalcInGivenOutParams
|
2022-02-14 17:27:36 +01:00
|
|
|
): Promise<OrderPriceAndFees> {
|
|
|
|
const orderPriceAndFee = {
|
|
|
|
price: '0',
|
2022-03-09 13:58:54 +01:00
|
|
|
publisherMarketOrderFee:
|
|
|
|
asset?.accessDetails?.publisherMarketOrderFee || '0',
|
2022-02-14 17:27:36 +01:00
|
|
|
publisherMarketPoolSwapFee: '0',
|
|
|
|
publisherMarketFixedSwapFee: '0',
|
2022-04-22 02:38:35 +02:00
|
|
|
consumeMarketOrderFee: consumeMarketOrderFee || '0',
|
2022-02-14 17:27:36 +01:00
|
|
|
consumeMarketPoolSwapFee: '0',
|
|
|
|
consumeMarketFixedSwapFee: '0',
|
2022-03-09 13:58:54 +01:00
|
|
|
providerFee: {
|
|
|
|
providerFeeAmount: '0'
|
|
|
|
},
|
2022-02-14 17:27:36 +01:00
|
|
|
opcFee: '0'
|
|
|
|
} as OrderPriceAndFees
|
|
|
|
|
|
|
|
// fetch provider fee
|
|
|
|
const initializeData = await ProviderInstance.initialize(
|
2022-03-09 13:58:54 +01:00
|
|
|
asset?.id,
|
2022-02-14 17:27:36 +01:00
|
|
|
asset.services[0].id,
|
|
|
|
0,
|
|
|
|
accountId,
|
2022-03-09 13:58:54 +01:00
|
|
|
asset?.services[0].serviceEndpoint
|
2022-02-14 17:27:36 +01:00
|
|
|
)
|
|
|
|
orderPriceAndFee.providerFee = initializeData.providerFee
|
|
|
|
|
|
|
|
// fetch price and swap fees
|
2022-03-09 13:58:54 +01:00
|
|
|
switch (asset?.accessDetails?.type) {
|
2022-02-14 17:27:36 +01:00
|
|
|
case 'dynamic': {
|
2022-04-22 02:38:35 +02:00
|
|
|
const poolPrice = calcInGivenOut(paramsForPool)
|
2022-02-16 19:42:35 +01:00
|
|
|
orderPriceAndFee.price = poolPrice.tokenAmount
|
|
|
|
orderPriceAndFee.liquidityProviderSwapFee =
|
|
|
|
poolPrice.liquidityProviderSwapFeeAmount
|
|
|
|
orderPriceAndFee.publisherMarketPoolSwapFee =
|
|
|
|
poolPrice.publishMarketSwapFeeAmount
|
|
|
|
orderPriceAndFee.consumeMarketPoolSwapFee =
|
|
|
|
poolPrice.consumeMarketSwapFeeAmount
|
2022-02-14 17:27:36 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'fixed': {
|
2022-03-09 13:58:54 +01:00
|
|
|
const fixed = await getFixedBuyPrice(asset?.accessDetails, asset?.chainId)
|
2022-02-14 17:27:36 +01:00
|
|
|
orderPriceAndFee.price = fixed.baseTokenAmount
|
2022-02-16 19:42:35 +01:00
|
|
|
orderPriceAndFee.opcFee = fixed.oceanFeeAmount
|
|
|
|
orderPriceAndFee.publisherMarketFixedSwapFee = fixed.marketFeeAmount
|
|
|
|
orderPriceAndFee.consumeMarketFixedSwapFee = fixed.consumeMarketFeeAmount
|
|
|
|
|
2022-02-14 17:27:36 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect
|
|
|
|
orderPriceAndFee.price = new Decimal(orderPriceAndFee.price)
|
|
|
|
.add(new Decimal(orderPriceAndFee.consumeMarketOrderFee))
|
|
|
|
.add(new Decimal(orderPriceAndFee.publisherMarketOrderFee))
|
|
|
|
.add(new Decimal(orderPriceAndFee.providerFee.providerFeeAmount))
|
|
|
|
.toString()
|
|
|
|
return orderPriceAndFee
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} chain
|
|
|
|
* @param {string} datatokenAddress
|
2022-03-09 13:58:54 +01:00
|
|
|
* @param {number} timeout timout of the service, this is needed to return order details
|
|
|
|
* @param {string} account account that wants to buy, is needed to return order details
|
|
|
|
* @param {bool} includeOrderPriceAndFees if false price will be spot price (pool) and rate (fre), if true you will get the order price including fees !! fees not yet done
|
2022-02-14 17:27:36 +01:00
|
|
|
* @returns {Promise<AccessDetails>}
|
2022-02-03 12:29:39 +01:00
|
|
|
*/
|
|
|
|
export async function getAccessDetails(
|
2022-02-14 17:27:36 +01:00
|
|
|
chainId: number,
|
2022-02-03 12:29:39 +01:00
|
|
|
datatokenAddress: string,
|
|
|
|
timeout?: number,
|
|
|
|
account = ''
|
|
|
|
): Promise<AccessDetails> {
|
2022-03-09 13:58:54 +01:00
|
|
|
try {
|
|
|
|
const queryContext = getQueryContext(Number(chainId))
|
|
|
|
const tokenQueryResult: OperationResult<
|
|
|
|
TokenPriceQuery,
|
|
|
|
{ datatokenId: string; account: string }
|
|
|
|
> = await fetchData(
|
|
|
|
TokenPriceQuery,
|
|
|
|
{
|
|
|
|
datatokenId: datatokenAddress.toLowerCase(),
|
|
|
|
account: account?.toLowerCase()
|
|
|
|
},
|
|
|
|
queryContext
|
|
|
|
)
|
2022-02-03 12:29:39 +01:00
|
|
|
|
2022-03-09 13:58:54 +01:00
|
|
|
const tokenPrice: TokenPrice = tokenQueryResult.data.token
|
|
|
|
const accessDetails = getAccessDetailsFromTokenPrice(tokenPrice, timeout)
|
|
|
|
return accessDetails
|
|
|
|
} catch (error) {
|
|
|
|
LoggerInstance.error('Error getting access details: ', error.message)
|
|
|
|
}
|
2022-02-03 12:29:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAccessDetailsForAssets(
|
|
|
|
assets: Asset[],
|
|
|
|
account = ''
|
|
|
|
): Promise<AssetExtended[]> {
|
|
|
|
const assetsExtended: AssetExtended[] = assets
|
2022-02-03 15:31:43 +01:00
|
|
|
const chainAssetLists: { [key: number]: string[] } = {}
|
2022-02-03 12:29:39 +01:00
|
|
|
|
2022-03-09 13:58:54 +01:00
|
|
|
try {
|
|
|
|
for (const asset of assets) {
|
|
|
|
if (chainAssetLists[asset.chainId]) {
|
|
|
|
chainAssetLists[asset.chainId].push(
|
|
|
|
asset.services[0].datatokenAddress.toLowerCase()
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
chainAssetLists[asset.chainId] = []
|
|
|
|
chainAssetLists[asset.chainId].push(
|
|
|
|
asset.services[0].datatokenAddress.toLowerCase()
|
|
|
|
)
|
|
|
|
}
|
2022-02-03 12:29:39 +01:00
|
|
|
}
|
|
|
|
|
2022-03-09 13:58:54 +01:00
|
|
|
for (const chainKey in chainAssetLists) {
|
|
|
|
const queryContext = getQueryContext(Number(chainKey))
|
|
|
|
const tokenQueryResult: OperationResult<
|
|
|
|
TokensPriceQuery,
|
|
|
|
{ datatokenIds: [string]; account: string }
|
|
|
|
> = await fetchData(
|
|
|
|
TokensPriceQuery,
|
|
|
|
{
|
|
|
|
datatokenIds: chainAssetLists[chainKey],
|
|
|
|
account: account?.toLowerCase()
|
|
|
|
},
|
|
|
|
queryContext
|
2022-02-14 17:27:36 +01:00
|
|
|
)
|
2022-03-09 13:58:54 +01:00
|
|
|
tokenQueryResult.data?.tokens.forEach((token) => {
|
|
|
|
const currentAsset = assetsExtended.find(
|
|
|
|
(asset) =>
|
|
|
|
asset.services[0].datatokenAddress.toLowerCase() === token.id
|
|
|
|
)
|
|
|
|
const accessDetails = getAccessDetailsFromTokenPrice(
|
|
|
|
token,
|
|
|
|
currentAsset?.services[0]?.timeout
|
|
|
|
)
|
2022-02-14 17:27:36 +01:00
|
|
|
|
2022-03-09 13:58:54 +01:00
|
|
|
currentAsset.accessDetails = accessDetails
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return assetsExtended
|
|
|
|
} catch (error) {
|
|
|
|
LoggerInstance.error('Error getting access details: ', error.message)
|
2022-02-03 12:29:39 +01:00
|
|
|
}
|
|
|
|
}
|