diff --git a/.changeset/fast-parents-accept.md b/.changeset/fast-parents-accept.md new file mode 100644 index 0000000..165b987 --- /dev/null +++ b/.changeset/fast-parents-accept.md @@ -0,0 +1,5 @@ +--- +"@mangrovedao/mgv": minor +--- + +Changes kandel client to receive action params diff --git a/.changeset/rotten-meals-bake.md b/.changeset/rotten-meals-bake.md new file mode 100644 index 0000000..99ee50d --- /dev/null +++ b/.changeset/rotten-meals-bake.md @@ -0,0 +1,5 @@ +--- +"@mangrovedao/mgv": patch +--- + +Added provisions and offer id to kandel view diff --git a/.changeset/weak-dolphins-boil.md b/.changeset/weak-dolphins-boil.md new file mode 100644 index 0000000..de083de --- /dev/null +++ b/.changeset/weak-dolphins-boil.md @@ -0,0 +1,5 @@ +--- +"@mangrovedao/mgv": minor +--- + +Changes getSemiBookOlKeys to get market params diff --git a/src/actions/book.ts b/src/actions/book.ts index 1dad5f9..16fce20 100644 --- a/src/actions/book.ts +++ b/src/actions/book.ts @@ -36,13 +36,9 @@ export async function getBook( ): Promise { const { depth = 100n, ...multicallParams } = parameters || {} const { mgv, mgvReader } = actionParams - const { base, quote, tickSpacing } = marketParams + const { base, quote } = marketParams - const { asksMarket, bidsMarket } = getSemibooksOLKeys({ - base: base.address, - quote: quote.address, - tickSpacing, - }) + const { asksMarket, bidsMarket } = getSemibooksOLKeys(marketParams) const [rpcAsks, rpcBids, rpcAsksConfig, rpcBaseConfig, rpcMarketConfig] = await getAction( diff --git a/src/actions/kandel/view.ts b/src/actions/kandel/view.ts index 8607c18..203044f 100644 --- a/src/actions/kandel/view.ts +++ b/src/actions/kandel/view.ts @@ -1,15 +1,27 @@ -import type { Address, Client, MulticallParameters } from 'viem' +import { + type Address, + type Client, + type MulticallParameters, + erc20Abi, +} from 'viem' import { multicall } from 'viem/actions' import { baseQuoteTickOffsetParams, getOfferParams, kandelParamsParams, + offerIdOfIndexParams, offeredVolumeParams, + provisionOfParams, } from '../../builder/kandel/view.js' -import { type MarketParams, priceFromTick } from '../../index.js' +import { + type MangroveActionsDefaultParams, + type MarketParams, + priceFromTick, +} from '../../index.js' import { BA } from '../../lib/enums.js' import { rawPriceToHumanPrice } from '../../lib/human-readable.js' import { unpackOffer } from '../../lib/offer.js' +import { getSemibooksOLKeys } from '../../lib/ol-key.js' import { getAction } from '../../utils/getAction.js' export type GetKandelStateParams = {} @@ -18,11 +30,13 @@ export type GetKandelStateArgs = GetKandelStateParams & Omit type OfferParsed = { + id: bigint tick: bigint gives: bigint price: number ba: BA index: bigint + provision: bigint } export type GetKandelStateResult = { @@ -33,43 +47,56 @@ export type GetKandelStateResult = { pricePoints: number quoteAmount: bigint baseAmount: bigint + unlockedProvision: bigint asks: OfferParsed[] bids: OfferParsed[] } export async function getKandelState( client: Client, + actionsParams: MangroveActionsDefaultParams, market: MarketParams, kandel: Address, args: GetKandelStateArgs, ): Promise { - const [baseQuoteTickOffset, params, quoteAmount, baseAmount] = - await getAction( - client, - multicall, - 'multicall', - )({ - ...args, - contracts: [ - { - address: kandel, - ...baseQuoteTickOffsetParams, - }, - { - address: kandel, - ...kandelParamsParams, - }, - { - address: kandel, - ...offeredVolumeParams(BA.bids), - }, - { - address: kandel, - ...offeredVolumeParams(BA.asks), - }, - ], - allowFailure: true, - }) + const [ + baseQuoteTickOffset, + params, + quoteAmount, + baseAmount, + unlockedProvision, + ] = await getAction( + client, + multicall, + 'multicall', + )({ + ...args, + contracts: [ + { + address: kandel, + ...baseQuoteTickOffsetParams, + }, + { + address: kandel, + ...kandelParamsParams, + }, + { + address: kandel, + ...offeredVolumeParams(BA.bids), + }, + { + address: kandel, + ...offeredVolumeParams(BA.asks), + }, + { + address: actionsParams.mgv, + abi: erc20Abi, + functionName: 'balanceOf', + args: [kandel], + }, + ], + allowFailure: true, + }) const pricePoints = params.status === 'success' ? params.result.pricePoints : 0 @@ -93,39 +120,93 @@ export async function getKandelState( address: kandel, ...getOfferParams(BA.asks, BigInt(i)), }, + { + address: kandel, + ...offerIdOfIndexParams(BA.bids, BigInt(i)), + }, + { + address: kandel, + ...offerIdOfIndexParams(BA.asks, BigInt(i)), + }, ]), }) - asks.push( - ...offers - .filter((_, i) => i % 2 === 1) - .flatMap((offer, index) => - offer.status === 'success' - ? { ...unpackOffer(offer.result), index: BigInt(index) } - : [], - ) - .filter((o) => o.gives > 0n) - .map((offer) => ({ - ...offer, - ba: BA.asks, - price: rawPriceToHumanPrice(priceFromTick(-offer.tick), market), - })), - ) - bids.push( - ...offers - .filter((_, i) => i % 2 === 0) - .flatMap((offer, index) => - offer.status === 'success' - ? { ...unpackOffer(offer.result), index: BigInt(index) } - : [], - ) - .filter((o) => o.gives > 0n) - .map((offer) => ({ - ...offer, - ba: BA.bids, - price: rawPriceToHumanPrice(priceFromTick(offer.tick), market), - })), - ) + if (offers.length !== pricePoints * 4) { + throw new Error('unexpected number of offers') + } + + for (let index = 0; index < offers.length; index += 4) { + const [rawBid, rawAsk, rawBidId, rawAskId] = offers.slice( + index, + index + 4, + ) + if (rawBid?.status === 'success' && rawBidId?.status === 'success') { + const bid = unpackOffer(rawBid.result) + const bidId = rawBidId.result + if (bidId > 0n) { + bids.push({ + ...bid, + index: BigInt(index / 4), + id: bidId, + price: rawPriceToHumanPrice(priceFromTick(-bid.tick), market), + ba: BA.bids, + provision: 0n, + }) + } + } + if (rawAsk?.status === 'success' && rawAskId?.status === 'success') { + const ask = unpackOffer(rawAsk.result) + const askId = rawAskId.result + if (askId > 0n) { + asks.push({ + ...ask, + index: BigInt(index / 4), + id: askId, + price: rawPriceToHumanPrice(priceFromTick(ask.tick), market), + ba: BA.asks, + provision: 0n, + }) + } + } + } + + if (bids.length || asks.length) { + const { asksMarket, bidsMarket } = getSemibooksOLKeys(market) + const provisions = await getAction( + client, + multicall, + 'multicall', + )({ + allowFailure: true, + ...args, + contracts: [ + ...bids.map((bid) => ({ + address: kandel, + ...provisionOfParams(bidsMarket, bid.id), + })), + ...asks.map((ask) => ({ + address: kandel, + ...provisionOfParams(asksMarket, ask.id), + })), + ], + }) + for (let i = 0; i < bids.length && i < provisions.length; i++) { + if (bids[i]!.gives === 0n) continue + const value = provisions[i] + const provision = value?.status === 'success' ? value.result : 0n + bids[i]!.provision = provision + } + for ( + let i = 0; + i < asks.length && i + bids.length < provisions.length; + i++ + ) { + if (asks[i]!.gives === 0n) continue + const value = provisions[i + bids.length] + const provision = value?.status === 'success' ? value.result : 0n + asks[i]!.provision = provision + } + } } return { @@ -137,6 +218,8 @@ export async function getKandelState( gasreq: params.status === 'success' ? params.result.gasreq : 0, stepSize: params.status === 'success' ? params.result.stepSize : 0, pricePoints, + unlockedProvision: + unlockedProvision.status === 'success' ? unlockedProvision.result : 0n, quoteAmount: quoteAmount.status === 'success' ? quoteAmount.result : 0n, baseAmount: baseAmount.status === 'success' ? baseAmount.result : 0n, asks, diff --git a/src/builder/kandel/view.ts b/src/builder/kandel/view.ts index c85f71b..862faee 100644 --- a/src/builder/kandel/view.ts +++ b/src/builder/kandel/view.ts @@ -1,5 +1,7 @@ import { type ContractFunctionParameters, parseAbi } from 'viem' import { BA } from '../../lib/enums.js' +import type { OLKey } from '../../types/lib.js' +import { olKeyABIRaw } from '../structs.js' export const paramsStruct = 'struct Params { uint32 gasprice; uint24 gasreq; uint32 stepSize; uint32 pricePoints; }' as const @@ -7,11 +9,13 @@ export const paramsStruct = // ba: 0 is bid, 1 is ask export const viewKandelABI = parseAbi([ paramsStruct, + olKeyABIRaw, 'function baseQuoteTickOffset() public view returns (uint)', 'function params() public view returns (Params memory)', 'function offeredVolume(uint8 ba) public view returns (uint volume)', 'function getOffer(uint8 ba, uint index) public view returns (uint offer)', 'function offerIdOfIndex(uint8 ba, uint index) public view returns (uint offerId)', + 'function provisionOf(OLKey memory olKey, uint offerId) public view returns (uint provision)', ]) export const baseQuoteTickOffsetParams = { @@ -70,3 +74,14 @@ export function offerIdOfIndexParams(ba: BA, index: bigint) { 'address' > } + +export function provisionOfParams(olKey: OLKey, offerId: bigint) { + return { + abi: viewKandelABI, + functionName: 'provisionOf', + args: [olKey, offerId], + } satisfies Omit< + ContractFunctionParameters, + 'address' + > +} diff --git a/src/bundle/public/kandel-actions.ts b/src/bundle/public/kandel-actions.ts index 21cbe5d..6fa28d9 100644 --- a/src/bundle/public/kandel-actions.ts +++ b/src/bundle/public/kandel-actions.ts @@ -31,7 +31,11 @@ import { type GetKandelStateResult, getKandelState, } from '../../actions/kandel/view.js' -import type { KandelSteps, MarketParams } from '../../index.js' +import type { + KandelSteps, + MangroveActionsDefaultParams, + MarketParams, +} from '../../index.js' export type KandelSeederActions = { getKandelSteps: (args: GetKandelStepsArgs) => Promise @@ -55,22 +59,22 @@ export type KandelActions = { args: PopulateChunkArgs, ) => Promise simulateRetract: (args: RetractArgs) => Promise - getKandelState: (args: GetKandelStateArgs) => Promise + getKandelState: (args?: GetKandelStateArgs) => Promise } -export function kandelActions(market: MarketParams, kandel: Address) { +export function kandelActions( + actionParams: MangroveActionsDefaultParams, + market: MarketParams, + kandel: Address, +) { return (client: Client): KandelActions => ({ - getKandelSteps: (args: GetKandelStepsArgs) => - getKandelSteps(client, market, kandel, args), - simulateSetLogics: (args: SetLogicsArgs) => - simulateSetLogics(client, kandel, args), - simulatePopulate: (args: PopulateArgs) => - simulatePopulate(client, kandel, args), - simulatePopulateChunk: (args: PopulateChunkArgs) => + getKandelSteps: (args) => getKandelSteps(client, market, kandel, args), + simulateSetLogics: (args) => simulateSetLogics(client, kandel, args), + simulatePopulate: (args) => simulatePopulate(client, kandel, args), + simulatePopulateChunk: (args) => simulatePopulateChunk(client, kandel, args), - simulateRetract: (args: RetractArgs) => - simulateRetract(client, kandel, args), - getKandelState: (args: GetKandelStateArgs) => - getKandelState(client, market, kandel, args), + simulateRetract: (args) => simulateRetract(client, kandel, args), + getKandelState: (args = {}) => + getKandelState(client, actionParams, market, kandel, args), }) } diff --git a/src/lib/ol-key.ts b/src/lib/ol-key.ts index 363f93e..9962e8c 100644 --- a/src/lib/ol-key.ts +++ b/src/lib/ol-key.ts @@ -1,4 +1,5 @@ -import { type Address, type Hex, encodeAbiParameters, keccak256 } from 'viem' +import { type Hex, encodeAbiParameters, keccak256 } from 'viem' +import type { MarketParams } from '../types/index.js' import type { OLKey } from '../types/lib.js' /** @@ -40,24 +41,20 @@ export function hash(olKey: OLKey): Hex { * @param params.tickSpacing the tick spacing * @returns the OLKeys for the asks and bids market */ -export function getSemibooksOLKeys(params: { - base: Address - quote: Address - tickSpacing: bigint -}): { +export function getSemibooksOLKeys(market: MarketParams): { asksMarket: OLKey bidsMarket: OLKey } { return { asksMarket: { - outbound_tkn: params.base, - inbound_tkn: params.quote, - tickSpacing: params.tickSpacing, + outbound_tkn: market.base.address, + inbound_tkn: market.quote.address, + tickSpacing: market.tickSpacing, }, bidsMarket: { - outbound_tkn: params.quote, - inbound_tkn: params.base, - tickSpacing: params.tickSpacing, + outbound_tkn: market.quote.address, + inbound_tkn: market.base.address, + tickSpacing: market.tickSpacing, }, } }