Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,18 @@ UI configuration provider. Manages params to configure custom styling, component
<code>Custom components to be injected into widget layout</code>
</summary>

> | name | type | default value | description |
> | ------------------- | ----------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
> | `GeoBlockAlert` | ComponentType | `<GeoBlockAlert>` | Component replaces deposit button while `isGeoBlocked` config param is set to `true` |
> | `SanctionedAlert` | ComponentType | `<SanctionedAlert>` | Component replaces deposit button while `isSanctioned` config param is set to `true` |
> | `DepositMetaInfo` | ComponentType | `undefined` | Component is injected into deposit meta part of widget layout nearby TransactionOverviewDisclosure |
> | `WithdrawMetaInfo` | ComponentType | `undefined` | Component is injected into withdraw meta part of widget layout nearby WithdrawTransactionOverviewDisclosure |
> | `CustomDepositMeta` | ComponentType | `undefined` | Custom extra component injected above deposit meta section in the deposit tab panel (e.g., chart, info, etc.) |
> | `Image` | ComponentType<ImageProps> | `<img>` | Component optionally can be used to pass `nextjs` Image component to be used for assets rendering |
> | `LogoSpinner` | ComponentType<SVGProps<SVGElement>> | `<Spinner>` | Component is injected into widget pending transaction overlay. Assume using of spinning animation |
> | `DepositTermsOfUse` | ComponentType | `undefined` | Component is injected into `TermsOfUseOverlay` to extend default terms of use statement points |
> | `ActionButton` | ComponentType | `<ActionButton>` | Component overrides default `ActionButton` and has `ButtonProps` API |
> | name | type | default value | description |
> | ----------------------- | ----------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
> | `GeoBlockAlert` | ComponentType | `<GeoBlockAlert>` | Component replaces deposit button while `isGeoBlocked` config param is set to `true` |
> | `SanctionedAlert` | ComponentType | `<SanctionedAlert>` | Component replaces deposit button while `isSanctioned` config param is set to `true` |
> | `MaxSupplyReachedAlert` | ComponentType | `<MaxSupplyReachedAlert>` | Component rendered in the deposit meta when the expected total supply exceeds the max cap; warns that the deposit will not go through |
> | `DepositMetaInfo` | ComponentType | `undefined` | Component is injected into deposit meta part of widget layout nearby TransactionOverviewDisclosure |
> | `WithdrawMetaInfo` | ComponentType | `undefined` | Component is injected into withdraw meta part of widget layout nearby WithdrawTransactionOverviewDisclosure |
> | `CustomDepositMeta` | ComponentType | `undefined` | Custom extra component injected above deposit meta section in the deposit tab panel (e.g., chart, info, etc.) |
> | `Image` | ComponentType<ImageProps> | `<img>` | Component optionally can be used to pass `nextjs` Image component to be used for assets rendering |
> | `LogoSpinner` | ComponentType<SVGProps<SVGElement>> | `<Spinner>` | Component is injected into widget pending transaction overlay. Assume using of spinning animation |
> | `DepositTermsOfUse` | ComponentType | `undefined` | Component is injected into `TermsOfUseOverlay` to extend default terms of use statement points |
> | `ActionButton` | ComponentType | `<ActionButton>` | Component overrides default `ActionButton` and has `ButtonProps` API |

###### Source: `packages/trading-widget/src/trading-widget/providers/component-provider/component-provider.tsx`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dhedge/trading-widget",
"version": "4.3.0",
"version": "4.4.0",
"license": "MIT",
"type": "module",
"main": "dist/index.cjs",
Expand Down
26 changes: 26 additions & 0 deletions src/core-kit/abi/pool-manager-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,30 @@ export const PoolManagerLogicAbi = [
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'maxSupplyCap',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'poolFeeShareNumerator',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
] as const
8 changes: 8 additions & 0 deletions src/core-kit/hooks/pool/multicall/use-pool-manager.static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ const getContracts = ({
args: [account ?? AddressZero],
chainId,
},
{
address,
abi: PoolManagerLogicAbi,
functionName: 'maxSupplyCap',
args: [],
chainId,
},
] as const

type Data = MulticallReturnType<ReturnType<typeof getContracts>>
Expand All @@ -46,6 +53,7 @@ const selector = (data: Data) => ({
getFeeIncreaseInfo: data[0].result,
minDepositUSD: data[1].result,
isMemberAllowed: data[2].result,
maxSupplyCapD18: data[3].result,
})

export const usePoolManagerStatic = ({
Expand Down
4 changes: 2 additions & 2 deletions src/core-kit/hooks/pool/multicall/use-pool.dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const selector = ([tokenPrice, getFundSummary]: Data) => {

return {
tokenPrice: tokenPrice?.result?.toString(),
totalValue: summary?.totalFundValue?.toString(),
totalSupply: summary?.totalSupply?.toString(),
totalValueD18: summary?.totalFundValue?.toString(),
totalSupplyD18: summary?.totalSupply?.toString(),
isPrivateVault: summary?.privatePool,
performanceFee: summary?.performanceFeeNumerator?.toString(),
streamingFee: summary?.managerFeeNumerator?.toString(),
Expand Down
82 changes: 82 additions & 0 deletions src/core-kit/hooks/pool/use-available-manager-fee.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as wagmi from 'wagmi'

import { DEFAULT_PRECISION, optimism } from 'core-kit/const'
import * as multicallHooks from 'core-kit/hooks/pool/multicall'
import { useAvailableManagerFee } from 'core-kit/hooks/pool/use-available-manager-fee'
import * as stateHooks from 'core-kit/hooks/state'
import { shiftBy } from 'core-kit/utils'
import { TEST_ADDRESS } from 'tests/mocks'
import { renderHook } from 'tests/test-utils'

const toD18String = (value: string | number) =>
shiftBy(value, DEFAULT_PRECISION)

vi.mock('core-kit/hooks/state', () => ({
useTradingPanelPoolConfig: vi.fn(),
}))

vi.mock('core-kit/hooks/pool/multicall', () => ({
usePoolManagerStatic: vi.fn(),
usePoolDynamic: vi.fn(),
}))

vi.mock('wagmi', async () => {
const actual = await vi.importActual<Record<string, unknown>>('wagmi')
return {
...actual,
useReadContract: vi.fn(),
}
})

describe('useAvailableManagerFee', () => {
beforeEach(() => {
vi.mocked(stateHooks.useTradingPanelPoolConfig).mockReturnValue({
address: TEST_ADDRESS,
chainId: optimism.id,
} as unknown as ReturnType<typeof stateHooks.useTradingPanelPoolConfig>)
vi.mocked(wagmi.useReadContract).mockReset()
})

it('disables query when no cap or total value', () => {
vi.mocked(multicallHooks.usePoolManagerStatic).mockReturnValue({
data: { maxSupplyCapD18: 0n },
} as unknown as ReturnType<typeof multicallHooks.usePoolManagerStatic>)
vi.mocked(multicallHooks.usePoolDynamic).mockReturnValue({
data: { totalValueD18: undefined },
} as unknown as ReturnType<typeof multicallHooks.usePoolDynamic>)
vi.mocked(wagmi.useReadContract).mockImplementationOnce((args: any) => {
expect(args.query?.enabled).toBe(false)
return {
data: undefined,
} as unknown as ReturnType<typeof wagmi.useReadContract>
})

const { result } = renderHook(() => useAvailableManagerFee())
expect(result.current.data).toBeUndefined()
})

it('calls with totalValue and rounds the result up', () => {
vi.mocked(multicallHooks.usePoolManagerStatic).mockReturnValue({
data: { maxSupplyCapD18: BigInt(toD18String(1000)) },
} as unknown as ReturnType<typeof multicallHooks.usePoolManagerStatic>)
vi.mocked(multicallHooks.usePoolDynamic).mockReturnValue({
data: { totalValueD18: toD18String(123.4567) },
} as unknown as ReturnType<typeof multicallHooks.usePoolDynamic>)

let capturedArgs: any
vi.mocked(wagmi.useReadContract).mockImplementationOnce((args: any) => {
capturedArgs = args
const raw = BigInt(toD18String(9.01))
const selected = args.query?.select ? args.query.select(raw) : raw
return {
data: selected,
} as unknown as ReturnType<typeof wagmi.useReadContract>
})

const { result } = renderHook(() => useAvailableManagerFee())
expect(result.current.data).toBe(10)
expect(capturedArgs.functionName).toBe('calculateAvailableManagerFee')
expect(capturedArgs.args?.[0]).toBe(BigInt(toD18String(123.4567)))
expect(capturedArgs.query?.enabled).toBe(true)
})
})
32 changes: 32 additions & 0 deletions src/core-kit/hooks/pool/use-available-manager-fee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useReadContract } from 'wagmi'

import { PoolLogicAbi } from 'core-kit/abi'
import {
usePoolDynamic,
usePoolManagerStatic,
} from 'core-kit/hooks/pool/multicall'
import { useTradingPanelPoolConfig } from 'core-kit/hooks/state'
import { normalizeNumber } from 'core-kit/utils'

const select = (data: bigint) => Math.ceil(normalizeNumber(data))

export const useAvailableManagerFee = () => {
const { address, chainId } = useTradingPanelPoolConfig()
const { data: { totalValueD18 } = {} } = usePoolDynamic({ address, chainId })
const { data: { maxSupplyCapD18 } = {} } = usePoolManagerStatic({
address,
chainId,
})

return useReadContract({
address,
chainId,
abi: PoolLogicAbi,
functionName: 'calculateAvailableManagerFee',
args: [BigInt(totalValueD18 ?? '0')],
query: {
enabled: !!totalValueD18 && !!maxSupplyCapD18 && maxSupplyCapD18 > 0n,
select,
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('formatPoolComposition', () => {
formatPoolComposition({
composition: [poolComposition],
vaultTokensAmount: 'assetAmount',
totalSupply: 'totalSupply',
totalSupplyD18: 'totalSupply',
}),
).toEqual([
{
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('formatPoolComposition', () => {
formatPoolComposition({
composition: [poolComposition, zeroAmountPoolComposition],
vaultTokensAmount: 'assetAmount',
totalSupply: 'totalSupply',
totalSupplyD18: 'totalSupply',
}),
).toEqual([
{
Expand Down Expand Up @@ -128,15 +128,15 @@ describe('usePoolCompositionWithFraction', () => {
},
}
const vaultTokensAmount = '2'
const totalSupply = undefined
const totalSupplyD18 = undefined

vi.mocked(poolHooks.usePoolComposition).mockImplementation(() => [
poolComposition,
])
vi.mocked(poolMulticallHooks.usePoolDynamic).mockImplementation(
() =>
({
data: { totalSupply },
data: { totalSupplyD18 },
}) as unknown as ReturnType<typeof poolMulticallHooks.usePoolDynamic>,
)

Expand Down Expand Up @@ -164,7 +164,7 @@ describe('usePoolCompositionWithFraction', () => {
},
}
const vaultTokensAmount = '2'
const totalSupply = '10'
const totalSupplyD18 = '10'
const fraction = 1
const fractionUsd = '2'

Expand All @@ -176,7 +176,7 @@ describe('usePoolCompositionWithFraction', () => {
vi.mocked(poolMulticallHooks.usePoolDynamic).mockImplementation(
() =>
({
data: { totalSupply },
data: { totalSupplyD18 },
}) as unknown as ReturnType<typeof poolMulticallHooks.usePoolDynamic>,
)

Expand All @@ -192,7 +192,7 @@ describe('usePoolCompositionWithFraction', () => {
formatPoolComposition({
composition: [poolComposition],
vaultTokensAmount: shiftBy(vaultTokensAmount || 0),
totalSupply: totalSupply.toString(),
totalSupplyD18: totalSupplyD18.toString(),
}),
)
})
Expand Down
16 changes: 8 additions & 8 deletions src/core-kit/hooks/pool/use-pool-composition-with-fraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
interface PoolCompositionParams {
composition: PoolComposition[]
vaultTokensAmount: string
totalSupply: string
totalSupplyD18: string
}

type PoolCompositionWithFractionParams = PoolContractCallParams & {
Expand All @@ -30,7 +30,7 @@ type PoolCompositionWithFractionParams = PoolContractCallParams & {
export const formatPoolComposition = ({
composition,
vaultTokensAmount,
totalSupply,
totalSupplyD18,
}: PoolCompositionParams): PoolCompositionWithFraction[] =>
composition
.reduce<PoolComposition[]>((acc, asset) => {
Expand Down Expand Up @@ -58,7 +58,7 @@ export const formatPoolComposition = ({
const fraction = getPoolFraction(
asset.amount,
vaultTokensAmount,
totalSupply,
totalSupplyD18,
asset.precision,
)
const fractionUsd = getPoolFraction(
Expand All @@ -67,7 +67,7 @@ export const formatPoolComposition = ({
.shiftedBy(-asset.precision)
.toFixed(),
vaultTokensAmount,
totalSupply,
totalSupplyD18,
)
return {
...asset,
Expand All @@ -85,16 +85,16 @@ export const usePoolCompositionWithFraction = ({
chainId,
}: PoolCompositionWithFractionParams) => {
const poolComposition = usePoolComposition({ address, chainId })
const { data: { totalSupply } = {} } = usePoolDynamic({ address, chainId })
const { data: { totalSupplyD18 } = {} } = usePoolDynamic({ address, chainId })

return useMemo(() => {
if (!totalSupply) {
if (!totalSupplyD18) {
return []
}
return formatPoolComposition({
composition: poolComposition,
vaultTokensAmount: shiftBy(vaultTokensAmount || 0),
totalSupply,
totalSupplyD18,
})
}, [vaultTokensAmount, poolComposition, totalSupply])
}, [vaultTokensAmount, poolComposition, totalSupplyD18])
}
Loading