Skip to content

Commit

Permalink
Merge pull request #21 from mangrovedao/feat/humanPrices
Browse files Browse the repository at this point in the history
Feat/human prices
  • Loading branch information
maxencerb authored May 5, 2024
2 parents 585c994 + 57a436f commit 574eba2
Show file tree
Hide file tree
Showing 18 changed files with 315 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-books-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mangrovedao/mgv": minor
---

Added general actions for balances and logic
5 changes: 5 additions & 0 deletions .changeset/metal-toes-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mangrovedao/mgv": patch
---

Added tickSpacing to tick lib
5 changes: 5 additions & 0 deletions .changeset/tough-ads-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mangrovedao/mgv": minor
---

Add human readable prices conversions
8 changes: 8 additions & 0 deletions .zed/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"formatter": {
"external": {
"command": "bun",
"arguments": ["run", "format", "--stdin-file-path={buffer_path}"]
}
}
}
137 changes: 137 additions & 0 deletions src/actions/balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {
type Address,
type Client,
erc20Abi,
isAddressEqual,
zeroAddress,
} from 'viem'
import type { ContractFunctionParameters, MulticallParameters } from 'viem'
import { multicall } from 'viem/actions'
import type { Logic, OverlyingResponse } from '../addresses/logics/utils.js'
import type { Token } from '../addresses/tokens/utils.js'
import type { MarketParams } from '../types/actions/index.js'
import type { Prettify } from '../types/lib.js'
import { getAction } from '../utils/getAction.js'

/**
* Get the balances for a user
* @param logics the logics to get balances for
* @param markets the markets to get balances for
* @param user the user to get balances for
*/
export type GetBalancesParams<TLogics extends Logic[] = Logic[]> = {
logics: TLogics
markets: MarketParams[]
user: Address
}

export type GetBalancesArgs<TLogics extends Logic[] = Logic[]> =
GetBalancesParams<TLogics> &
Omit<MulticallParameters, 'allowFailure' | 'contracts'>

type ExtendedOverlyingResponse = Prettify<OverlyingResponse & { token: Token }>

export type GetBalanceResult<TLogics extends Logic[] = Logic[]> = {
tokens: {
token: Token
balance: bigint
}[]
overlying: ExtendedOverlyingResponse[]
logicBalances: {
token: Token
logic: TLogics[number]
balance: bigint
}[]
}

export async function getBalances<TLogics extends Logic[] = Logic[]>(
client: Client,
args: GetBalancesArgs<TLogics>,
): Promise<GetBalanceResult<TLogics>> {
const { logics, markets, ...multicallArgs } = args
const tokens = markets.reduce((acc, market) => {
if (!acc.find((t) => isAddressEqual(t.address, market.base.address))) {
acc.push(market.base)
}
if (!acc.find((t) => isAddressEqual(t.address, market.quote.address))) {
acc.push(market.quote)
}
return acc
}, [] as Token[])

const tokenBalanceCalls = tokens.map(
(token) =>
({
address: token.address,
abi: erc20Abi,
functionName: 'balanceOf',
args: [args.user],
}) as const,
)

const overlyingCalls = tokens.flatMap((token) =>
logics.map((logic) =>
logic.logicOverlying.getOverlyingContractParams({
token: token.address,
logic: logic.logic,
name: logic.name,
}),
),
) as ContractFunctionParameters[]

const logicBalancesCalls = tokens.flatMap((token) =>
logics.map((logic) =>
logic.logicBalance.getRoutingLogicBalanceParams({
token: token.address,
logic: logic.logic,
name: logic.name,
user: args.user,
}),
),
) as ContractFunctionParameters[]

const result = await getAction(
client,
multicall,
'multicall',
)({
...multicallArgs,
contracts: [...tokenBalanceCalls, ...overlyingCalls, ...logicBalancesCalls],
allowFailure: true,
})

const tokenBalances = tokens.map((token, i) => {
const res = result[i]
const balance = res.status === 'success' ? (res.result as bigint) : 0n
return { token, balance }
})

const overlying = tokens.flatMap((token, i) =>
logics.map((logic, j) => {
const res = result[tokens.length * (i + 1) + j]
const overlying: OverlyingResponse =
res.status === 'success'
? logic.logicOverlying.parseOverlyingContractResponse(res.result)
: {
type: 'erc20',
overlying: zeroAddress,
available: false,
}
return { token, ...overlying }
}),
)

const logicBalances = tokens.flatMap((token, i) =>
logics.map((logic, j) => {
const res = result[overlying.length + tokens.length * (i + 1) + j]
const balance = res.status === 'success' ? (res.result as bigint) : 0n
return { token, logic, balance }
}),
)

return {
tokens: tokenBalances,
overlying,
logicBalances,
}
}
8 changes: 6 additions & 2 deletions src/actions/order/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type GetLimitOrderStepsParams = {
userRouter: Address
bs: BS
sendAmount?: bigint
logicToken?: Address
}

export type GetLimitOrderStepsArgs = Prettify<
Expand All @@ -48,8 +49,11 @@ export async function getLimitOrderSteps(
args: GetLimitOrderStepsArgs,
): Promise<LimitOrderSteps> {
const { sendAmount: amount = maxUint256 } = args
const tokenToApprove =
args.bs === BS.buy ? market.quote.address : market.base.address
const tokenToApprove = args.logicToken
? args.logicToken
: args.bs === BS.buy
? market.quote.address
: market.base.address

const allowance = await getAction(
client,
Expand Down
6 changes: 6 additions & 0 deletions src/addresses/logics/chains/blast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export const blastPacFinanceLogic = buildLogic(
aaveOverLying,
aaveBalance,
)

export const blastLogics = [
blastOrbitLogic,
blastZeroLendLogic,
blastPacFinanceLogic,
] as const
1 change: 1 addition & 0 deletions src/addresses/logics/chains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export {
blastOrbitLogic,
blastZeroLendLogic,
blastPacFinanceLogic,
blastLogics,
} from './blast.js'
2 changes: 2 additions & 0 deletions src/addresses/logics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {
blastOrbitLogic,
blastZeroLendLogic,
blastPacFinanceLogic,
blastLogics,
} from './chains/index.js'

export {
Expand All @@ -19,6 +20,7 @@ export type {
LogicBalanceParams,
LogicBalanceResponse,
RoutingLogicBalance,
Logic,
} from './utils.js'

export { buildLogic } from './utils.js'
2 changes: 1 addition & 1 deletion src/addresses/logics/strategies/aave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const aaveOverLying: RoutingLogicOverlying<
parseOverlyingContractResponse(response) {
return {
type: 'erc20',
token: response,
overlying: response,
available: !isAddressEqual(response, zeroAddress),
}
},
Expand Down
4 changes: 3 additions & 1 deletion src/addresses/logics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type OverlyingParams = {

export type OverlyingResponse = {
type: 'erc20' | 'erc721'
token: Address
overlying: Address
available: boolean
}

Expand Down Expand Up @@ -96,3 +96,5 @@ export function buildLogic<
logicBalance,
}
}

export type Logic = ReturnType<typeof buildLogic>
2 changes: 1 addition & 1 deletion src/bundle/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { publicMarketActions } from './public/index.js'
export { publicMarketActions, generalActions } from './public/index.js'
24 changes: 24 additions & 0 deletions src/bundle/public/general-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Client } from 'viem'
import {
type GetBalanceResult,
type GetBalancesArgs,
getBalances,
} from '../../actions/balances.js'
import type { Logic } from '../../addresses/logics/utils.js'

export type GeneralActions = {
/**
*
* @param args.markets the markets to get balances for
* @param args.logics the logics to get balances for
* @param args.user the user to get balances for
* @returns balances of user for each token in markets, overlying tokens, and logic balances
*/
getBalances: <TLogics extends Logic[] = Logic[]>(
args: GetBalancesArgs<TLogics>,
) => Promise<GetBalanceResult<TLogics>>
}

export const generalActions = (client: Client): GeneralActions => ({
getBalances: (args) => getBalances(client, args),
})
1 change: 1 addition & 0 deletions src/bundle/public/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { publicMarketActions } from './market-actions.js'
export { generalActions } from './general-actions.js'
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export type {
MarketOrderResultFromLogsParams,
LimitOrderResultFromLogsParams,
LimitOrderResult,
AmountsToHumanPriceParams,
AmountsParams,
AmountsOutput,
} from './lib/index.js'

export {
Expand Down Expand Up @@ -61,7 +64,7 @@ export type {

// --- bundles ---

export { publicMarketActions } from './bundle/index.js'
export { publicMarketActions, generalActions } from './bundle/index.js'

// --- addresses ---

Expand Down
86 changes: 85 additions & 1 deletion src/lib/human-readable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { formatUnits } from 'viem'
import type { MarketParams } from '../types/index.js'
import type { BA } from './enums.js'
import { inboundFromOutbound } from './tick.js'
import {
inboundFromOutbound,
outboundFromInbound,
tickFromPrice,
tickFromVolumes,
} from './tick.js'

// if ask, then outbound is the base, inbound is the quote
// if bid, then outbound is the quote, inbound is the base
Expand Down Expand Up @@ -43,3 +49,81 @@ export function rpcOfferToHumanOffer({
ba,
}
}

export function rawPriceToHumanPrice(
price: number,
market: MarketParams,
): number {
// if market is WETH/USDC, and 1 WETH = 3000 USDC, WETH decimals = 18, USDC decimals = 6
// this means raw price is (3000 * 1e6) / (1 * 1e18) = 3000 * 1e(-12)
// So if we want to convert this to human readable price, we need to multiply by 1e12 = 1e(18 - 6) = 1e(baseDecimals - quoteDecimals)
return price * 10 ** (market.base.decimals - market.quote.decimals)
}

export function humanPriceToRawPrice(
price: number,
market: MarketParams,
): number {
return price * 10 ** (market.quote.decimals - market.base.decimals)
}

export type AmountsToHumanPriceParams = {
baseAmount: bigint
quoteAmount: bigint
}

export function amountsToHumanPrice(
params: AmountsToHumanPriceParams,
market: MarketParams,
): number {
const price = Number(params.quoteAmount) / Number(params.baseAmount)
return rawPriceToHumanPrice(price, market)
}

export type AmountsParams =
| {
baseAmount: bigint
quoteAmount: bigint
}
| {
humanPrice: number
baseAmount: bigint
}
| {
humanPrice: number
quoteAmount: bigint
}

export type AmountsOutput = {
baseAmount: bigint
quoteAmount: bigint
humanPrice: number
}

export function amounts(
params: AmountsParams,
market: MarketParams,
): AmountsOutput {
// quote is inbound, base is outbound
const tick =
'humanPrice' in params
? tickFromPrice(
humanPriceToRawPrice(params.humanPrice, market),
market.tickSpacing,
)
: tickFromVolumes(
params.quoteAmount,
params.baseAmount,
market.tickSpacing,
)
const baseAmount =
'baseAmount' in params
? params.baseAmount
: outboundFromInbound(tick, params.quoteAmount)
const quoteAmount =
'quoteAmount' in params
? params.quoteAmount
: inboundFromOutbound(tick, params.baseAmount)
const humanPrice = amountsToHumanPrice({ baseAmount, quoteAmount }, market)
return { baseAmount, quoteAmount, humanPrice }
}
Loading

0 comments on commit 574eba2

Please sign in to comment.