diff --git a/examples/market-order.ts b/examples/market-order.ts index 85a02d75..d8731115 100644 --- a/examples/market-order.ts +++ b/examples/market-order.ts @@ -17,7 +17,7 @@ const main = async () => { const { transaction, - result: { spend, take }, + result: { spent, taken }, } = await marketOrder({ chainId: arbitrumSepolia.id, userAddress: walletClient.account.address, @@ -30,8 +30,8 @@ const main = async () => { gasPrice: parseUnits('1', 9), }) console.log(`market order hash: ${hash}`) - console.log(`spend: `, spend) - console.log(`take: `, take) + console.log(`spend: `, spent) + console.log(`taken: `, taken) const market = await getMarket({ chainId: arbitrumSepolia.id, diff --git a/package-lock.json b/package-lock.json index 51147e64..0548e031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@clober/v2-sdk", - "version": "0.0.95", + "version": "0.0.96", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@clober/v2-sdk", - "version": "0.0.95", + "version": "0.0.96", "license": "MIT", "dependencies": { "axios": "^1.7.7", diff --git a/package.json b/package.json index 0e5e07bc..4b6fa362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@clober/v2-sdk", - "version": "0.0.95", + "version": "0.0.96", "description": "🛠 An SDK for building applications on top of Clober V2", "files": [ "dist" diff --git a/src/apis/market.ts b/src/apis/market.ts index a006d41f..05f5b44b 100644 --- a/src/apis/market.ts +++ b/src/apis/market.ts @@ -12,6 +12,7 @@ import { BOOK_VIEWER_ABI } from '../abis/core/book-viewer-abi' import { fetchIsMarketOpened } from '../utils/open' import { fetchCurrency } from '../utils/currency' import { Subgraph } from '../constants/subgraph' +import { FeePolicy } from '../model/fee-policy' const fetchBookFromSubgraph = async (chainId: CHAIN_IDS, bookId: string) => { return Subgraph.get<{ @@ -39,14 +40,17 @@ const getBook = async ( chainId: CHAIN_IDS, quoteCurrency: Currency, baseCurrency: Currency, + makerFeePolicy: FeePolicy, + takerFeePolicy: FeePolicy, useSubgraph: boolean, n: number, ): Promise => { const unitSize = await calculateUnitSize(publicClient, chainId, quoteCurrency) const bookId = toBookId( - chainId, quoteCurrency.address, baseCurrency.address, + makerFeePolicy, + takerFeePolicy, unitSize, ) if (useSubgraph) { @@ -68,6 +72,8 @@ const getBook = async ( ) : [], isOpened: book !== null, + makerFeePolicy, + takerFeePolicy, }) } @@ -92,6 +98,8 @@ const getBook = async ( unitAmount: depth, })), isOpened, + makerFeePolicy, + takerFeePolicy, }) } @@ -99,6 +107,10 @@ export async function fetchMarket( publicClient: PublicClient, chainId: CHAIN_IDS, tokenAddresses: `0x${string}`[], + bidBookMakerFeePolicy: FeePolicy, + bidBookTakerFeePolicy: FeePolicy, + askBookMakerFeePolicy: FeePolicy, + askBookTakerFeePolicy: FeePolicy, useSubgraph: boolean, n = 100, ): Promise { @@ -116,8 +128,26 @@ export async function fetchMarket( fetchCurrency(publicClient, chainId, baseTokenAddress), ]) const [bidBook, askBook] = await Promise.all([ - getBook(publicClient, chainId, quoteCurrency, baseCurrency, useSubgraph, n), - getBook(publicClient, chainId, baseCurrency, quoteCurrency, useSubgraph, n), + getBook( + publicClient, + chainId, + quoteCurrency, + baseCurrency, + bidBookMakerFeePolicy, + bidBookTakerFeePolicy, + useSubgraph, + n, + ), + getBook( + publicClient, + chainId, + baseCurrency, + quoteCurrency, + askBookMakerFeePolicy, + askBookTakerFeePolicy, + useSubgraph, + n, + ), ]) return new Market({ diff --git a/src/apis/open-order.ts b/src/apis/open-order.ts index bf75a6d7..324772dd 100644 --- a/src/apis/open-order.ts +++ b/src/apis/open-order.ts @@ -9,8 +9,8 @@ import { invertTick, toPrice } from '../utils/tick' import type { OpenOrder, OpenOrderDto } from '../model/open-order' import { fetchCurrency } from '../utils/currency' import { applyPercent } from '../utils/bigint' -import { MAKER_DEFAULT_POLICY } from '../constants/fee' import { Subgraph } from '../constants/subgraph' +import { FeePolicy } from '../model/fee-policy' const getOpenOrderFromSubgraph = async ( chainId: CHAIN_IDS, @@ -70,6 +70,7 @@ export async function fetchOpenOrdersByUserAddress( publicClient: PublicClient, chainId: CHAIN_IDS, userAddress: `0x${string}`, + makerFeePolicy: FeePolicy, ): Promise { const { data: { openOrders }, @@ -88,7 +89,7 @@ export async function fetchOpenOrdersByUserAddress( .map((address) => fetchCurrency(publicClient, chainId, address)), ) return openOrders.map((openOrder) => - toOpenOrder(chainId, currencies, openOrder), + toOpenOrder(chainId, currencies, openOrder, makerFeePolicy), ) } @@ -96,6 +97,7 @@ export async function fetchOpenOrder( publicClient: PublicClient, chainId: CHAIN_IDS, id: string, + makerFeePolicy: FeePolicy, ): Promise { const { data: { openOrder }, @@ -107,13 +109,14 @@ export async function fetchOpenOrder( fetchCurrency(publicClient, chainId, getAddress(openOrder.book.base.id)), fetchCurrency(publicClient, chainId, getAddress(openOrder.book.quote.id)), ]) - return toOpenOrder(chainId, currencies, openOrder) + return toOpenOrder(chainId, currencies, openOrder, makerFeePolicy) } export async function fetchOpenOrders( publicClient: PublicClient, chainId: CHAIN_IDS, ids: string[], + makerFeePolicy: FeePolicy, ): Promise { const { data: { openOrders }, @@ -132,7 +135,7 @@ export async function fetchOpenOrders( .map((address) => fetchCurrency(publicClient, chainId, address)), ) return openOrders.map((openOrder) => - toOpenOrder(chainId, currencies, openOrder), + toOpenOrder(chainId, currencies, openOrder, makerFeePolicy), ) } @@ -140,6 +143,7 @@ const toOpenOrder = ( chainId: CHAIN_IDS, currencies: Currency[], openOrder: OpenOrderDto, + makerFeePolicy: FeePolicy, ): OpenOrder => { const inputCurrency = currencies.find((c: Currency) => isAddressEqual(c.address, getAddress(openOrder.book.quote.id)), @@ -210,8 +214,8 @@ const toOpenOrder = ( applyPercent( cancelable, 100 + - (Number(MAKER_DEFAULT_POLICY[chainId].rate) * 100) / - Number(MAKER_DEFAULT_POLICY[chainId].RATE_PRECISION), + (Number(makerFeePolicy.rate) * 100) / + Number(makerFeePolicy.RATE_PRECISION), 6, ), inputCurrency.decimals, diff --git a/src/apis/pool.ts b/src/apis/pool.ts index 25c17606..27bbea86 100644 --- a/src/apis/pool.ts +++ b/src/apis/pool.ts @@ -13,6 +13,7 @@ import { REBALANCER_ABI } from '../abis/rebalancer/rebalancer-abi' import { CHART_LOG_INTERVALS, Market } from '../type' import { Subgraph } from '../constants/subgraph' import { STRATEGY_ABI } from '../abis/rebalancer/strategy-abi' +import { MAKER_DEFAULT_POLICY, TAKER_DEFAULT_POLICY } from '../constants/fee' import { fetchMarket } from './market' @@ -68,7 +69,16 @@ export async function fetchPool( } if (!market) { market = ( - await fetchMarket(publicClient, chainId, tokenAddresses, useSubgraph) + await fetchMarket( + publicClient, + chainId, + tokenAddresses, + MAKER_DEFAULT_POLICY[chainId], + TAKER_DEFAULT_POLICY[chainId], + MAKER_DEFAULT_POLICY[chainId], + TAKER_DEFAULT_POLICY[chainId], + useSubgraph, + ) ).toJson() } const poolKey = toPoolKey( diff --git a/src/call.ts b/src/call.ts index 07bde562..27b151e0 100644 --- a/src/call.ts +++ b/src/call.ts @@ -48,6 +48,7 @@ import { OPERATOR_ABI } from './abis/rebalancer/operator-abi' import { STRATEGY_ABI } from './abis/rebalancer/strategy-abi' import { ELECTION_GOVERNOR_ABI } from './abis/governance/election-governor-abi' import { VCLOB_ABI } from './abis/governance/vclob-abi' +import { FeePolicy } from './model/fee-policy' /** * Build a transaction to open a market. @@ -81,16 +82,30 @@ export const openMarket = async ({ outputToken: `0x${string}` options?: DefaultWriteContractOptions & { useSubgraph?: boolean + makerFeePolicy?: FeePolicy + takerFeePolicy?: FeePolicy } }): Promise => { const publicClient = createPublicClient({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [makerFeePolicy, takerFeePolicy] = [ + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.takerFeePolicy + ? options.takerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] const market = await fetchMarket( publicClient, chainId, [inputToken, outputToken], + makerFeePolicy, + takerFeePolicy, + makerFeePolicy, + takerFeePolicy, !!(options && options.useSubgraph), ) const isBid = isAddressEqual(market.quote.address, inputToken) @@ -118,9 +133,9 @@ export const openMarket = async ({ base: outputToken, unitSize, quote: inputToken, - makerPolicy: MAKER_DEFAULT_POLICY[chainId].value, + makerPolicy: makerFeePolicy.value, hooks: zeroAddress, - takerPolicy: TAKER_DEFAULT_POLICY[chainId].value, + takerPolicy: takerFeePolicy.value, }, hookData: zeroHash, }, @@ -204,6 +219,10 @@ export const limitOrder = async ({ roundingDownTakenBid?: boolean roundingUpTakenAsk?: boolean useSubgraph?: boolean + bidBookMakerFeePolicy?: FeePolicy + bidBookTakerFeePolicy?: FeePolicy + askBookMakerFeePolicy?: FeePolicy + askBookTakerFeePolicy?: FeePolicy } & DefaultWriteContractOptions }): Promise<{ transaction: Transaction @@ -228,10 +247,33 @@ export const limitOrder = async ({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [ + bidBookMakerFeePolicy, + bidBookTakerFeePolicy, + askBookMakerFeePolicy, + askBookTakerFeePolicy, + ] = [ + options && options.bidBookMakerFeePolicy + ? options.bidBookMakerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.bidBookTakerFeePolicy + ? options.bidBookTakerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + options && options.askBookMakerFeePolicy + ? options.askBookMakerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.askBookTakerFeePolicy + ? options.askBookTakerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] const market = await fetchMarket( publicClient, chainId, [inputToken, outputToken], + bidBookMakerFeePolicy, + bidBookTakerFeePolicy, + askBookMakerFeePolicy, + askBookTakerFeePolicy, !!(options && options.useSubgraph), ) const isBid = isAddressEqual(market.quote.address, inputToken) @@ -274,12 +316,20 @@ export const limitOrder = async ({ options: { ...options, limitPrice: price, + makerFeePolicy: isBid ? askBookMakerFeePolicy : bidBookMakerFeePolicy, + takerFeePolicy: isBid ? askBookTakerFeePolicy : bidBookTakerFeePolicy, }, }), ]) const isETH = isAddressEqual(inputToken, zeroAddress) const makeParam = { - id: toBookId(chainId, inputToken, outputToken, unitSize), + id: toBookId( + inputToken, + outputToken, + isBid ? bidBookMakerFeePolicy : askBookMakerFeePolicy, + isBid ? bidBookTakerFeePolicy : askBookTakerFeePolicy, + unitSize, + ), tick: options?.makeTick ? Number(options.makeTick) : Number( @@ -477,6 +527,8 @@ export const marketOrder = async ({ roundingDownTakenBid?: boolean roundingUpTakenAsk?: boolean useSubgraph?: boolean + makerFeePolicy?: FeePolicy + takerFeePolicy?: FeePolicy } & DefaultWriteContractOptions }): Promise<{ transaction: Transaction @@ -504,10 +556,22 @@ export const marketOrder = async ({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [makerFeePolicy, takerFeePolicy] = [ + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.takerFeePolicy + ? options.takerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] const market = await fetchMarket( publicClient, chainId, [inputToken, outputToken], + makerFeePolicy, + takerFeePolicy, + makerFeePolicy, + takerFeePolicy, !!(options && options.useSubgraph), ) const isTakingBid = isAddressEqual(market.base.address, inputToken) @@ -759,6 +823,7 @@ export const claimOrders = async ({ ids: string[] options?: DefaultWriteContractOptions & { useSubgraph?: boolean + makerFeePolicy?: FeePolicy } }): Promise<{ transaction: Transaction; result: CurrencyFlow[] }> => { const publicClient = createPublicClient({ @@ -787,6 +852,9 @@ export const claimOrders = async ({ publicClient, chainId, ids.map((id) => BigInt(id)), + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], !!(options && options.useSubgraph), ) ).filter( @@ -920,6 +988,7 @@ export const cancelOrders = async ({ ids: string[] options?: DefaultWriteContractOptions & { useSubgraph?: boolean + makerFeePolicy?: FeePolicy } }): Promise<{ transaction: Transaction; result: CurrencyFlow[] }> => { const publicClient = createPublicClient({ @@ -948,6 +1017,9 @@ export const cancelOrders = async ({ publicClient, chainId, ids.map((id) => BigInt(id)), + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], !!(options && options.useSubgraph), ) ).filter( @@ -1033,6 +1105,8 @@ export const openPool = async ({ salt: `0x${string}` options?: DefaultWriteContractOptions & { useSubgraph?: boolean + makerPolicy?: FeePolicy + takerPolicy?: FeePolicy } }): Promise => { const publicClient = createPublicClient({ @@ -1060,17 +1134,29 @@ export const openPool = async ({ base: pool.market.bidBook.base.address, unitSize: pool.market.bidBook.unitSize, quote: pool.market.bidBook.quote.address, - makerPolicy: MAKER_DEFAULT_POLICY[chainId].value, + makerPolicy: + options && options.makerPolicy + ? options.makerPolicy + : MAKER_DEFAULT_POLICY[chainId].value, hooks: zeroAddress, - takerPolicy: TAKER_DEFAULT_POLICY[chainId].value, + takerPolicy: + options && options.takerPolicy + ? options.takerPolicy + : TAKER_DEFAULT_POLICY[chainId].value, }, { base: pool.market.askBook.base.address, unitSize: pool.market.askBook.unitSize, quote: pool.market.askBook.quote.address, - makerPolicy: MAKER_DEFAULT_POLICY[chainId].value, + makerPolicy: + options && options.makerPolicy + ? options.makerPolicy + : MAKER_DEFAULT_POLICY[chainId].value, hooks: zeroAddress, - takerPolicy: TAKER_DEFAULT_POLICY[chainId].value, + takerPolicy: + options && options.takerPolicy + ? options.takerPolicy + : TAKER_DEFAULT_POLICY[chainId].value, }, toBytes32(salt), CONTRACT_ADDRESSES[chainId]!.Strategy, diff --git a/src/model/book.ts b/src/model/book.ts index 27d29262..18fb4dcd 100644 --- a/src/model/book.ts +++ b/src/model/book.ts @@ -1,4 +1,3 @@ -import { TAKER_DEFAULT_POLICY } from '../constants/fee' import { divide } from '../utils/math' import { baseToQuote, quoteToBase } from '../utils/decimals' import { CHAIN_IDS } from '../constants/chain' @@ -6,6 +5,7 @@ import { MIN_TICK } from '../constants/tick' import type { Currency } from './currency' import type { DepthDto } from './depth' +import { FeePolicy } from './fee-policy' export class Book { chainId: CHAIN_IDS @@ -15,6 +15,8 @@ export class Book { quote: Currency depths: DepthDto[] isOpened: boolean + makerFeePolicy: FeePolicy + takerFeePolicy: FeePolicy constructor({ chainId, @@ -24,6 +26,8 @@ export class Book { unitSize, depths, isOpened, + makerFeePolicy, + takerFeePolicy, }: { chainId: CHAIN_IDS id: bigint @@ -32,6 +36,8 @@ export class Book { unitSize: bigint depths: DepthDto[] isOpened: boolean + makerFeePolicy: FeePolicy + takerFeePolicy: FeePolicy }) { this.chainId = chainId this.id = id @@ -40,6 +46,8 @@ export class Book { this.quote = quote this.depths = depths this.isOpened = isOpened + this.makerFeePolicy = makerFeePolicy + this.takerFeePolicy = takerFeePolicy } take = ({ @@ -73,8 +81,8 @@ export class Book { if (limitTick > tick) { break } - let maxAmount = TAKER_DEFAULT_POLICY[this.chainId].usesQuote - ? TAKER_DEFAULT_POLICY[this.chainId].calculateOriginalAmount( + let maxAmount = this.takerFeePolicy.usesQuote + ? this.takerFeePolicy.calculateOriginalAmount( amountOut - takenQuoteAmount, true, ) @@ -90,14 +98,12 @@ export class Book { ? maxAmount : currentDepth.unitAmount) * this.unitSize let baseAmount = quoteToBase(tick, quoteAmount, true) - if (TAKER_DEFAULT_POLICY[this.chainId].usesQuote) { + if (this.takerFeePolicy.usesQuote) { quoteAmount = - quoteAmount - - TAKER_DEFAULT_POLICY[this.chainId].calculateFee(quoteAmount, false) + quoteAmount - this.takerFeePolicy.calculateFee(quoteAmount, false) } else { baseAmount = - baseAmount + - TAKER_DEFAULT_POLICY[this.chainId].calculateFee(baseAmount, false) + baseAmount + this.takerFeePolicy.calculateFee(baseAmount, false) } if (quoteAmount === 0n) { break @@ -153,9 +159,9 @@ export class Book { if (limitTick > tick) { break } - let maxAmount = TAKER_DEFAULT_POLICY[this.chainId].usesQuote + let maxAmount = this.takerFeePolicy.usesQuote ? amountIn - spentBaseAmount - : TAKER_DEFAULT_POLICY[this.chainId].calculateOriginalAmount( + : this.takerFeePolicy.calculateOriginalAmount( amountIn - spentBaseAmount, false, ) @@ -170,14 +176,12 @@ export class Book { ? maxAmount : currentDepth.unitAmount) * this.unitSize let baseAmount = quoteToBase(tick, quoteAmount, true) - if (TAKER_DEFAULT_POLICY[this.chainId].usesQuote) { + if (this.takerFeePolicy.usesQuote) { quoteAmount = - quoteAmount - - TAKER_DEFAULT_POLICY[this.chainId].calculateFee(quoteAmount, false) + quoteAmount - this.takerFeePolicy.calculateFee(quoteAmount, false) } else { baseAmount = - baseAmount + - TAKER_DEFAULT_POLICY[this.chainId].calculateFee(baseAmount, false) + baseAmount + this.takerFeePolicy.calculateFee(baseAmount, false) } if (baseAmount === 0n) { break diff --git a/src/model/market.ts b/src/model/market.ts index 0ef80a72..9fcd63eb 100644 --- a/src/model/market.ts +++ b/src/model/market.ts @@ -4,9 +4,8 @@ import { getMarketId } from '../utils/market' import { CHAIN_IDS } from '../constants/chain' import { invertTick, toPrice } from '../utils/tick' import { formatPrice } from '../utils/prices' -import { MAKER_DEFAULT_POLICY, TAKER_DEFAULT_POLICY } from '../constants/fee' import { quoteToBase } from '../utils/decimals' -import { Market as MarketType } from '../type' +import { FeePolicy, Market as MarketType } from '../type' import { Book } from './book' import type { Currency } from './currency' @@ -14,8 +13,10 @@ import type { Depth } from './depth' export class Market { chainId: CHAIN_IDS - makerFee: number - takerFee: number + bidBookMakerFeePolicy: FeePolicy + bidBookTakerFeePolicy: FeePolicy + askBookMakerFeePolicy: FeePolicy + askBookTakerFeePolicy: FeePolicy id: string quote: Currency @@ -49,8 +50,10 @@ export class Market { isAddressEqual(token.address, baseTokenAddress!), )! - this.makerFee = (Number(MAKER_DEFAULT_POLICY[chainId].rate) * 100) / 1e6 - this.takerFee = (Number(TAKER_DEFAULT_POLICY[chainId].rate) * 100) / 1e6 + this.bidBookMakerFeePolicy = bidBook.makerFeePolicy + this.bidBookTakerFeePolicy = bidBook.takerFeePolicy + this.askBookMakerFeePolicy = askBook.makerFeePolicy + this.askBookTakerFeePolicy = askBook.takerFeePolicy this.bids = bidBook.depths.map( (depth) => @@ -149,8 +152,6 @@ export class Market { chainId: this.chainId, quote: this.quote, base: this.base, - makerFee: this.makerFee, - takerFee: this.takerFee, bids: this.bids.map(({ price, tick, baseAmount }) => ({ price, tick: Number(tick), @@ -162,6 +163,16 @@ export class Market { unitSize: this.bidBook.unitSize.toString(), quote: this.bidBook.quote, isOpened: this.bidBook.isOpened, + makerFee: { + fee: (Number(this.bidBook.makerFeePolicy.rate) * 100) / 1e6, + rate: Number(this.bidBook.makerFeePolicy.rate), + usesQuote: this.bidBook.makerFeePolicy.usesQuote, + }, + takerFee: { + fee: (Number(this.bidBook.takerFeePolicy.rate) * 100) / 1e6, + rate: Number(this.bidBook.takerFeePolicy.rate), + usesQuote: this.bidBook.takerFeePolicy.usesQuote, + }, }, asks: this.asks.map(({ price, tick, baseAmount }) => ({ price, @@ -174,6 +185,16 @@ export class Market { unitSize: this.askBook.unitSize.toString(), quote: this.askBook.quote, isOpened: this.askBook.isOpened, + makerFee: { + fee: (Number(this.askBook.makerFeePolicy.rate) * 100) / 1e6, + rate: Number(this.askBook.makerFeePolicy.rate), + usesQuote: this.askBook.makerFeePolicy.usesQuote, + }, + takerFee: { + fee: (Number(this.askBook.takerFeePolicy.rate) * 100) / 1e6, + rate: Number(this.askBook.takerFeePolicy.rate), + usesQuote: this.askBook.takerFeePolicy.usesQuote, + }, }, } } diff --git a/src/type.ts b/src/type.ts index f8ca19bf..bce152dc 100644 --- a/src/type.ts +++ b/src/type.ts @@ -4,6 +4,7 @@ import { CHAIN_IDS } from './constants/chain' import type { Currency, Currency6909 } from './model/currency' export { CHAIN_IDS } from './constants/chain' +export { FeePolicy } from './model/fee-policy' export type { Currency } from './model/currency' export type { OpenOrder } from './model/open-order' @@ -19,6 +20,16 @@ export type Book = { unitSize: string quote: Currency isOpened: boolean + makerFee: { + fee: number + rate: number + usesQuote: boolean + } + takerFee: { + fee: number + rate: number + usesQuote: boolean + } } export type LastRawAmounts = { @@ -37,8 +48,6 @@ export type Market = { chainId: CHAIN_IDS quote: Currency base: Currency - makerFee: number - takerFee: number bids: Depth[] bidBook: Book asks: Depth[] diff --git a/src/utils/book-id.ts b/src/utils/book-id.ts index 69961542..bfbc9d08 100644 --- a/src/utils/book-id.ts +++ b/src/utils/book-id.ts @@ -1,12 +1,12 @@ import { encodeAbiParameters, keccak256, zeroAddress } from 'viem' -import { MAKER_DEFAULT_POLICY, TAKER_DEFAULT_POLICY } from '../constants/fee' -import { CHAIN_IDS } from '../constants/chain' +import { FeePolicy } from '../model/fee-policy' export const toBookId = ( - chainId: CHAIN_IDS, quote: `0x${string}`, base: `0x${string}`, + makerPolicy: FeePolicy, + takerPolicy: FeePolicy, unitSize: bigint, ) => { const value = keccak256( @@ -23,9 +23,9 @@ export const toBookId = ( base, unitSize, quote, - Number(MAKER_DEFAULT_POLICY[chainId].value), + Number(makerPolicy.value), zeroAddress, - Number(TAKER_DEFAULT_POLICY[chainId].value), + Number(takerPolicy.value), ], ), ) diff --git a/src/utils/order.ts b/src/utils/order.ts index 7e9f5def..7b01a55f 100644 --- a/src/utils/order.ts +++ b/src/utils/order.ts @@ -3,9 +3,9 @@ import { formatUnits, getAddress, isAddressEqual, PublicClient } from 'viem' import { CHAIN_IDS, getMarketPrice } from '../index' import { CONTRACT_ADDRESSES } from '../constants/addresses' import { fetchOpenOrders } from '../apis/open-order' -import { MAKER_DEFAULT_POLICY } from '../constants/fee' import { BOOK_MANAGER_ABI } from '../abis/core/book-manager-abi' import { OnChainOpenOrder } from '../model/open-order' +import { FeePolicy } from '../model/fee-policy' import { fetchCurrencyMap } from './currency' import { quoteToBase } from './decimals' @@ -16,6 +16,7 @@ export const fetchOnChainOrders = async ( publicClient: PublicClient, chainId: CHAIN_IDS, orderIds: bigint[], + makerFeePolicy: FeePolicy, useSubgraph: boolean, ): Promise => { if (useSubgraph) { @@ -23,6 +24,7 @@ export const fetchOnChainOrders = async ( publicClient, chainId, orderIds.map((orderId) => orderId.toString()), + makerFeePolicy, ) return openOrders.map( ({ @@ -102,8 +104,8 @@ export const fetchOnChainOrders = async ( const cancelable = applyPercent( unitSize * order.open, 100 + - (Number(MAKER_DEFAULT_POLICY[chainId].rate) * 100) / - Number(MAKER_DEFAULT_POLICY[chainId].RATE_PRECISION), + (Number(makerFeePolicy.rate) * 100) / + Number(makerFeePolicy.RATE_PRECISION), 6, ) const claimable = quoteToBase( diff --git a/src/view.ts b/src/view.ts index ea7d6550..cbc8749e 100644 --- a/src/view.ts +++ b/src/view.ts @@ -23,7 +23,7 @@ import type { StrategyPosition, VCLOB, } from './type' -import { CHART_LOG_INTERVALS, ElectionCandidate } from './type' +import { CHART_LOG_INTERVALS, ElectionCandidate, FeePolicy } from './type' import { formatPrice, parsePrice } from './utils/prices' import { fetchOpenOrder, fetchOpenOrdersByUserAddress } from './apis/open-order' import { OpenOrder } from './model/open-order' @@ -49,6 +49,7 @@ import { fetchVCLOBList } from './apis/vclob' import { ELECTION_GOVERNOR_ABI } from './abis/governance/election-governor-abi' import { VCLOB_ABI } from './abis/governance/vclob-abi' import { KEEPERS_REGISTRY_ABI } from './abis/governance/keepers-registry-abi' +import { MAKER_DEFAULT_POLICY, TAKER_DEFAULT_POLICY } from './constants/fee' /** * Get contract addresses by chain id @@ -132,6 +133,10 @@ export const getMarket = async ({ options?: { n?: number useSubgraph?: boolean + bidBookMakerFeePolicy?: FeePolicy + bidBookTakerFeePolicy?: FeePolicy + askBookMakerFeePolicy?: FeePolicy + askBookTakerFeePolicy?: FeePolicy } & DefaultReadContractOptions }): Promise => { if (isAddressEqual(token0, token1)) { @@ -141,10 +146,34 @@ export const getMarket = async ({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [ + bidBookMakerFeePolicy, + bidBookTakerFeePolicy, + askBookMakerFeePolicy, + askBookTakerFeePolicy, + ] = [ + options && options.bidBookMakerFeePolicy + ? options.bidBookMakerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.bidBookTakerFeePolicy + ? options.bidBookTakerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + options && options.askBookMakerFeePolicy + ? options.askBookMakerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.askBookTakerFeePolicy + ? options.askBookTakerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] + const market = await fetchMarket( publicClient, chainId, [token0, token1], + bidBookMakerFeePolicy, + bidBookTakerFeePolicy, + askBookMakerFeePolicy, + askBookTakerFeePolicy, !!(options && options.useSubgraph), options?.n, ) @@ -831,6 +860,8 @@ export const getExpectedOutput = async ({ roundingDownTakenBid?: boolean roundingUpTakenAsk?: boolean useSubgraph?: boolean + makerFeePolicy?: FeePolicy + takerFeePolicy?: FeePolicy } & DefaultReadContractOptions }): Promise<{ takenAmount: string @@ -846,10 +877,22 @@ export const getExpectedOutput = async ({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [makerFeePolicy, takerFeePolicy] = [ + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.takerFeePolicy + ? options.takerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] const market = await fetchMarket( publicClient, chainId, [inputToken, outputToken], + makerFeePolicy, + takerFeePolicy, + makerFeePolicy, + takerFeePolicy, !!(options && options.useSubgraph), ) const isBid = isAddressEqual(market.quote.address, inputToken) @@ -949,6 +992,8 @@ export const getExpectedInput = async ({ roundingDownTakenBid?: boolean roundingUpTakenAsk?: boolean useSubgraph?: boolean + makerFeePolicy?: FeePolicy + takerFeePolicy?: FeePolicy } & DefaultReadContractOptions }): Promise<{ takenAmount: string @@ -964,10 +1009,22 @@ export const getExpectedInput = async ({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) + const [makerFeePolicy, takerFeePolicy] = [ + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + options && options.takerFeePolicy + ? options.takerFeePolicy + : TAKER_DEFAULT_POLICY[chainId], + ] const market = await fetchMarket( publicClient, chainId, [inputToken, outputToken], + makerFeePolicy, + takerFeePolicy, + makerFeePolicy, + takerFeePolicy, !!(options && options.useSubgraph), ) const isBid = isAddressEqual(market.quote.address, inputToken) @@ -1044,13 +1101,22 @@ export const getOpenOrder = async ({ }: { chainId: CHAIN_IDS id: string - options?: DefaultReadContractOptions + options?: DefaultReadContractOptions & { + makerFeePolicy?: FeePolicy + } }): Promise => { const publicClient = createPublicClient({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) - return fetchOpenOrder(publicClient, chainId, id) + return fetchOpenOrder( + publicClient, + chainId, + id, + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + ) } /** * Retrieves open orders for the specified user on the given chain. @@ -1074,13 +1140,22 @@ export const getOpenOrders = async ({ }: { chainId: CHAIN_IDS userAddress: `0x${string}` - options?: DefaultReadContractOptions + options?: DefaultReadContractOptions & { + makerFeePolicy?: FeePolicy + } }): Promise => { const publicClient = createPublicClient({ chain: CHAIN_MAP[chainId], transport: options?.rpcUrl ? http(options.rpcUrl) : http(), }) - return fetchOpenOrdersByUserAddress(publicClient, chainId, userAddress) + return fetchOpenOrdersByUserAddress( + publicClient, + chainId, + userAddress, + options && options.makerFeePolicy + ? options.makerFeePolicy + : MAKER_DEFAULT_POLICY[chainId], + ) } /** diff --git a/test/book-id.test.ts b/test/book-id.test.ts index dce1d294..21ff9489 100644 --- a/test/book-id.test.ts +++ b/test/book-id.test.ts @@ -76,22 +76,32 @@ test('check toBookId function', async () => { base: '0x0000000000000000000000000000000000000000' as `0x${string}`, unit: 10n ** 12n, quote: '0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8' as `0x${string}`, - makerPolicy: Number(MAKER_DEFAULT_POLICY[arbitrumSepolia.id].value), + makerPolicy: MAKER_DEFAULT_POLICY[arbitrumSepolia.id], hooks: '0x0000000000000000000000000000000000000000' as `0x${string}`, - takerPolicy: Number(TAKER_DEFAULT_POLICY[arbitrumSepolia.id].value), + takerPolicy: TAKER_DEFAULT_POLICY[arbitrumSepolia.id], } expect( await publicClient.readContract({ address: BOOK_ID_WRAPPER_ADDRESS, abi: _abi, functionName: 'toId', - args: [bidBookKey], + args: [ + { + base: bidBookKey.base, + unit: bidBookKey.unit, + quote: bidBookKey.quote, + makerPolicy: Number(bidBookKey.makerPolicy.value), + hooks: bidBookKey.hooks, + takerPolicy: Number(bidBookKey.takerPolicy.value), + }, + ], }), ).toBe( toBookId( - arbitrumSepolia.id, bidBookKey.quote, bidBookKey.base, + bidBookKey.makerPolicy, + bidBookKey.takerPolicy, bidBookKey.unit, ), ) @@ -100,22 +110,32 @@ test('check toBookId function', async () => { base: '0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8' as `0x${string}`, unit: 10n ** 1n, quote: '0x0000000000000000000000000000000000000000' as `0x${string}`, - makerPolicy: Number(MAKER_DEFAULT_POLICY[arbitrumSepolia.id].value), + makerPolicy: MAKER_DEFAULT_POLICY[arbitrumSepolia.id], hooks: '0x0000000000000000000000000000000000000000' as `0x${string}`, - takerPolicy: Number(TAKER_DEFAULT_POLICY[arbitrumSepolia.id].value), + takerPolicy: TAKER_DEFAULT_POLICY[arbitrumSepolia.id], } expect( await publicClient.readContract({ address: BOOK_ID_WRAPPER_ADDRESS, abi: _abi, functionName: 'toId', - args: [askBookKey], + args: [ + { + base: askBookKey.base, + unit: askBookKey.unit, + quote: askBookKey.quote, + makerPolicy: Number(askBookKey.makerPolicy.value), + hooks: askBookKey.hooks, + takerPolicy: Number(askBookKey.takerPolicy.value), + }, + ], }), ).toBe( toBookId( - arbitrumSepolia.id, askBookKey.quote, askBookKey.base, + askBookKey.makerPolicy, + askBookKey.takerPolicy, askBookKey.unit, ), ) diff --git a/test/pool-key.test.ts b/test/pool-key.test.ts index e89ca152..efaa754c 100644 --- a/test/pool-key.test.ts +++ b/test/pool-key.test.ts @@ -4,6 +4,10 @@ import { arbitrumSepolia } from 'viem/chains' import { toBookId } from '../src/utils/book-id' import { toPoolKey } from '../src/utils/pool-key' +import { + MAKER_DEFAULT_POLICY, + TAKER_DEFAULT_POLICY, +} from '../src/constants/fee' import { FORK_URL } from './utils/constants' @@ -47,15 +51,17 @@ const publicClient = createPublicClient({ }) const BID_BOOK_ID = toBookId( - arbitrumSepolia.id, '0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8', '0x0000000000000000000000000000000000000000', + MAKER_DEFAULT_POLICY[arbitrumSepolia.id], + TAKER_DEFAULT_POLICY[arbitrumSepolia.id], 10n ** 12n, ) const ASK_BID_ID = toBookId( - arbitrumSepolia.id, '0x0000000000000000000000000000000000000000', '0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8', + MAKER_DEFAULT_POLICY[arbitrumSepolia.id], + TAKER_DEFAULT_POLICY[arbitrumSepolia.id], 1n, ) const SALT =