perps: modify perps, update contracts, interest, realized profits (#783)

This commit is contained in:
Bob van der Helm 2024-02-08 15:25:41 +01:00 committed by GitHub
parent 9762aa7cd0
commit 6d5e7c7325
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 711 additions and 508 deletions

View File

@ -16,7 +16,7 @@ export default function DropDownButton(props: Props) {
content={<DropDown closeMenu={() => toggleIsOpen(false)} {...props} />} content={<DropDown closeMenu={() => toggleIsOpen(false)} {...props} />}
type='info' type='info'
placement='bottom' placement='bottom'
contentClassName='!bg-white/10 border border-white/20 backdrop-blur-xl !p-0' contentClassName='!bg-white/10 backdrop-blur-xl !p-0'
interactive interactive
hideArrow hideArrow
visible={isOpen} visible={isOpen}

View File

@ -1,3 +1,4 @@
import { Row } from '@tanstack/react-table'
import { useMemo } from 'react' import { useMemo } from 'react'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
@ -18,6 +19,10 @@ export const SIZE_META = {
), ),
} }
export const sizeSortingFn = (a: Row<PerpPositionRow>, b: Row<PerpPositionRow>): number => {
return a.original.amount.abs().minus(b.original.amount.abs()).toNumber()
}
type Props = { type Props = {
amount: BigNumber amount: BigNumber
asset: Asset asset: Asset
@ -27,7 +32,7 @@ export default function Size(props: Props) {
const price = usePrice(props.asset.denom) const price = usePrice(props.asset.denom)
const amount = useMemo( const amount = useMemo(
() => demagnify(props.amount.toString(), props.asset), () => demagnify(props.amount.abs().toString(), props.asset),
[props.asset, props.amount], [props.asset, props.amount],
) )
const value = useMemo(() => price.times(amount).toNumber(), [amount, price]) const value = useMemo(() => price.times(amount).toNumber(), [amount, price])

View File

@ -6,7 +6,7 @@ import Leverage, { LEVERAGE_META } from 'components/perps/BalancesTable/Columns/
import Manage, { MANAGE_META } from 'components/perps/BalancesTable/Columns/Manage' import Manage, { MANAGE_META } from 'components/perps/BalancesTable/Columns/Manage'
import { PERP_NAME_META, PerpName } from 'components/perps/BalancesTable/Columns/PerpName' import { PERP_NAME_META, PerpName } from 'components/perps/BalancesTable/Columns/PerpName'
import PnL, { PNL_META } from 'components/perps/BalancesTable/Columns/PnL' import PnL, { PNL_META } from 'components/perps/BalancesTable/Columns/PnL'
import Size, { SIZE_META } from 'components/perps/BalancesTable/Columns/Size' import Size, { SIZE_META, sizeSortingFn } from 'components/perps/BalancesTable/Columns/Size'
import TradeDirection, { import TradeDirection, {
PERP_TYPE_META, PERP_TYPE_META,
} from 'components/perps/BalancesTable/Columns/TradeDirection' } from 'components/perps/BalancesTable/Columns/TradeDirection'
@ -25,6 +25,7 @@ export default function usePerpsBalancesTable() {
{ {
...SIZE_META, ...SIZE_META,
cell: ({ row }) => <Size amount={row.original.amount} asset={row.original.asset} />, cell: ({ row }) => <Size amount={row.original.amount} asset={row.original.asset} />,
sortingFn: sizeSortingFn,
}, },
{ {
...LEVERAGE_META, ...LEVERAGE_META,

View File

@ -132,6 +132,7 @@ export function PerpsModule() {
previousTradeDirection={previousTradeDirection} previousTradeDirection={previousTradeDirection}
previousLeverage={previousLeverage} previousLeverage={previousLeverage}
hasActivePosition={hasActivePosition} hasActivePosition={hasActivePosition}
onTxExecuted={() => setAmount(BN_ZERO)}
/> />
</Card> </Card>
) )

View File

@ -8,6 +8,7 @@ import SummaryLine from 'components/common/SummaryLine'
import Text from 'components/common/Text' import Text from 'components/common/Text'
import TradeDirection from 'components/perps/BalancesTable/Columns/TradeDirection' import TradeDirection from 'components/perps/BalancesTable/Columns/TradeDirection'
import OpeningFee from 'components/perps/Module/OpeningFee' import OpeningFee from 'components/perps/Module/OpeningFee'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@ -18,37 +19,56 @@ type Props = {
amount: BigNumber amount: BigNumber
tradeDirection: TradeDirection tradeDirection: TradeDirection
asset: Asset asset: Asset
previousAmount?: BigNumber previousAmount?: BigNumber | null
previousTradeDirection?: 'long' | 'short' previousTradeDirection?: 'long' | 'short'
previousLeverage?: number previousLeverage?: number | null
hasActivePosition: boolean hasActivePosition: boolean
onTxExecuted: () => void
} }
export default function PerpsSummary(props: Props) { export default function PerpsSummary(props: Props) {
const openPerpPosition = useStore((s) => s.openPerpPosition) const openPerpPosition = useStore((s) => s.openPerpPosition)
const modifyPerpPosition = useStore((s) => s.modifyPerpPosition)
const closePerpPosition = useStore((s) => s.closePerpPosition)
const currentAccount = useCurrentAccount() const currentAccount = useCurrentAccount()
const onConfirm = useCallback(async () => { const newAmount = useMemo(
if (!currentAccount) return () => (props.previousAmount ?? BN_ZERO).plus(props.amount),
await openPerpPosition({
accountId: currentAccount.id,
coin: BNCoin.fromDenomAndBigNumber(
props.asset.denom,
props.amount.times(props.tradeDirection === 'short' ? -1 : 1),
),
})
}, [currentAccount, openPerpPosition, props.amount, props.asset.denom, props.tradeDirection])
const disabled = useMemo(
() =>
(props.previousAmount && props.previousAmount.isEqualTo(props.amount)) ||
props.amount.isZero(),
[props.amount, props.previousAmount], [props.amount, props.previousAmount],
) )
const onConfirm = useCallback(async () => {
if (!currentAccount) return
if (props.previousAmount && newAmount.isZero()) {
await closePerpPosition({
accountId: currentAccount.id,
denom: props.asset.denom,
})
return props.onTxExecuted()
}
if (props.previousAmount && newAmount) {
await modifyPerpPosition({
accountId: currentAccount.id,
coin: BNCoin.fromDenomAndBigNumber(props.asset.denom, newAmount),
changeDirection: props.previousAmount.isNegative() !== newAmount.isNegative(),
})
return props.onTxExecuted()
}
await openPerpPosition({
accountId: currentAccount.id,
coin: BNCoin.fromDenomAndBigNumber(props.asset.denom, props.amount),
})
return props.onTxExecuted()
}, [closePerpPosition, currentAccount, modifyPerpPosition, newAmount, openPerpPosition, props])
const disabled = useMemo(() => props.amount.isZero(), [props.amount])
return ( return (
<div className='border border-white/10 rounded-sm bg-white/5'> <div className='border border-white/10 rounded-sm bg-white/5'>
<ManageSummary {...props} /> <ManageSummary {...props} newAmount={newAmount} />
<div className='py-4 px-3 flex flex-col gap-1'> <div className='py-4 px-3 flex flex-col gap-1'>
<Text size='xs' className='font-bold mb-2'> <Text size='xs' className='font-bold mb-2'>
Summary Summary
@ -67,9 +87,9 @@ export default function PerpsSummary(props: Props) {
) )
} }
function ManageSummary(props: Props) { function ManageSummary(props: Props & { newAmount: BigNumber }) {
const showTradeDirection = const showTradeDirection =
props.previousTradeDirection && props.previousTradeDirection !== props.tradeDirection props.previousAmount && props.previousAmount.isNegative() !== props.newAmount.isNegative()
const showAmount = !props.amount.isZero() && props.previousAmount const showAmount = !props.amount.isZero() && props.previousAmount
const showLeverage = const showLeverage =
props.previousLeverage && props.previousLeverage &&
@ -84,7 +104,13 @@ function ManageSummary(props: Props) {
Your new position Your new position
</Text> </Text>
{showTradeDirection && props.previousTradeDirection && ( {props.newAmount.isZero() && (
<Text size='xs' className='text-white/40 mb-1'>
Your position will be closed
</Text>
)}
{showTradeDirection && props.previousTradeDirection && !props.newAmount.isZero() && (
<SummaryLine label='Side' contentClassName='flex gap-1'> <SummaryLine label='Side' contentClassName='flex gap-1'>
<TradeDirection tradeDirection={props.previousTradeDirection} /> <TradeDirection tradeDirection={props.previousTradeDirection} />
<ArrowRight width={16} /> <ArrowRight width={16} />
@ -92,13 +118,15 @@ function ManageSummary(props: Props) {
</SummaryLine> </SummaryLine>
)} )}
{showAmount && props.previousAmount && ( {showAmount && props.newAmount && props.previousAmount && !props.newAmount.isZero() && (
<SummaryLine label='Size' contentClassName='flex gap-1'> <SummaryLine label='Size' contentClassName='flex gap-1'>
<AssetAmount asset={props.asset} amount={props.previousAmount.abs().toNumber()} /> <AssetAmount asset={props.asset} amount={props.previousAmount.abs().toNumber()} />
<ArrowRight <ArrowRight
width={16} width={16}
className={classNames( className={classNames(
props.previousAmount.isGreaterThan(props.amount) ? 'text-error' : 'text-success', props.previousAmount.abs().isGreaterThan(props.newAmount)
? 'text-error'
: 'text-success',
)} )}
/> />
<AssetAmount <AssetAmount

View File

@ -29,11 +29,7 @@ export default function usePerpsModule(amount: BigNumber | null) {
return getAccountNetValue(account, prices, assets) return getAccountNetValue(account, prices, assets)
}, [account, assets, prices]) }, [account, assets, prices])
const previousAmount = useMemo( const previousAmount = useMemo(() => perpPosition?.amount ?? BN_ZERO, [perpPosition?.amount])
() =>
(perpPosition?.amount ?? BN_ZERO).times(perpPosition?.tradeDirection === 'short' ? -1 : 1),
[perpPosition?.amount, perpPosition?.tradeDirection],
)
const previousTradeDirection = useMemo( const previousTradeDirection = useMemo(
() => perpPosition?.tradeDirection || 'long', () => perpPosition?.tradeDirection || 'long',
[perpPosition?.tradeDirection], [perpPosition?.tradeDirection],
@ -41,18 +37,16 @@ export default function usePerpsModule(amount: BigNumber | null) {
const previousLeverage = useMemo( const previousLeverage = useMemo(
() => () =>
price previousAmount
.times(demagnify(previousAmount.abs(), perpsAsset)) ? price.times(demagnify(previousAmount, perpsAsset)).div(accountNetValue).plus(1).toNumber()
.div(accountNetValue) : null,
.plus(1)
.toNumber(),
[accountNetValue, perpsAsset, previousAmount, price], [accountNetValue, perpsAsset, previousAmount, price],
) )
const leverage = useMemo( const leverage = useMemo(
() => () =>
price price
.times(demagnify(previousAmount.plus(amount ?? BN_ZERO).abs(), perpsAsset)) .times(demagnify(previousAmount.plus(amount ?? BN_ZERO), perpsAsset))
.div(accountNetValue) .div(accountNetValue)
.plus(1) .plus(1)
.toNumber(), .toNumber(),

View File

@ -21,14 +21,14 @@ const Pion1: ChainConfig = {
id: ChainInfoID.Pion1, id: ChainInfoID.Pion1,
name: 'Neutron Testnet', name: 'Neutron Testnet',
contracts: { contracts: {
redBank: 'neutron1u3fmnnd3q9dkx2sccjgs2hfdp7sndkw5f37ug64zj0zgdm6dgjxq4njrnc', redBank: 'neutron1gpv59ff87mfvx8s3fn5ku3l8dn94fkj4q37sruhwfl0zzgjsejqs3xejyj',
incentives: 'neutron18ere5chtsdswna8re75sldzhw0ccrmuff57cfh63060dnvflqswsxpzlm9', incentives: 'neutron1au78lscqqh77ghvl6eq2d58vy439q0gprhz0sc5q4r9svh63hquqtwlrsw',
oracle: 'neutron1nua7c2esr5d8f6jfkylsd4pjywwlf0snj8gtxkp6k6f5jxa32hxqq2lcm7', oracle: 'neutron1z44naqtpn20z5yws7updsgcplm7tcfkcn67uejvc0l7n8hy6vupq0894qs',
swapper: 'neutron1me0pspfhkphe2mw7ja2f2fqnh0z5x30jj5t80jgd7p6y9swwk06snh7mun', swapper: 'neutron1td4dn53k7ythdj8ah3xv5p66swq3sy2a9jzq4yrue8aa4dvwacrs7dachf',
params: 'neutron1ku3eccj8a49atmgkv4g8r2zgy62l93xy224ls44lfvdexh5xfqds97rrz3', params: 'neutron16l9j74q6ht9ycd37qxt6tz83l3r3cwec4qu9r5mkd66kcve23ywspjqhjp',
creditManager: 'neutron1zjuschuar2e9cugj84hmun4r93mzkan6qy4x4ee5geguh3lmrdnsnq5s9z', creditManager: 'neutron1swud86k6acvpjfn68cv6tz3h7z72nz395d7y5e3yxknrc8yl9faqxn5jjw',
accountNft: 'neutron12wrdnp0edn7xqleak4khn265xrtkk732ext4d3yeyfz2gcup3e4scmmrj3', accountNft: 'neutron1apus79ka5v30wmwdxzzapprrzxxw6mz0hc0uk3g030t0m5f0asuq8x3ldf',
perps: 'neutron1zlj4l4h55wmrctm3ete6sqjvucujgcpa7r9m0ex6pcgdngaqa0pss93jla', perps: 'neutron1ssnc38h40fu0d2et8f38z83ealgugh06r4anqa6dn6hlz6syqaqsmj6zcy',
pyth: 'neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t', pyth: 'neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t',
}, },
endpoints: { endpoints: {

View File

@ -1,6 +1,5 @@
import useSWR from 'swr' import useSWR from 'swr'
import { BN_ZERO } from 'constants/math'
import usePerpsAsset from 'hooks/perps/usePerpsAsset' import usePerpsAsset from 'hooks/perps/usePerpsAsset'
import useChainConfig from 'hooks/useChainConfig' import useChainConfig from 'hooks/useChainConfig'
import useClients from 'hooks/useClients' import useClients from 'hooks/useClients'
@ -27,8 +26,8 @@ async function getPerpsMarket(clients: ContractClients, asset: Asset) {
fundingRate: BN(denomState.rate as any), fundingRate: BN(denomState.rate as any),
asset: asset, asset: asset,
openInterest: { openInterest: {
long: BN_ZERO, long: BN(denomState.long_oi),
short: BN_ZERO, short: BN(denomState.short_oi),
}, },
} as PerpsMarket } as PerpsMarket
} }

View File

@ -246,6 +246,11 @@ export function useUpdatedAccount(account?: Account) {
const simulatePerps = useCallback( const simulatePerps = useCallback(
(position: PerpsPosition) => { (position: PerpsPosition) => {
if (!account) return if (!account) return
if (position.amount.isZero()) {
return addPerps(undefined)
}
addPerps(position) addPerps(position)
}, },
[account, addPerps], [account, addPerps],

View File

@ -126,11 +126,18 @@ export default function createBroadcastSlice(
}) })
break break
case 'close-perp': case 'close-perp':
// TODO: [Perps] Elaborate on the message
toast.content.push({ toast.content.push({
coins: changes.deposits?.map((deposit) => deposit.toCoin()) ?? [], coins: [],
text: 'Closed perp position', text: 'Closed perp position',
}) })
break break
case 'modify-perp':
toast.content.push({
coins: [],
text: 'Modified perp position',
})
break
case 'swap': case 'swap':
if (changes.debts) { if (changes.debts) {
@ -995,6 +1002,57 @@ export default function createBroadcastSlice(
return response.then((response) => !!response.result) return response.then((response) => !!response.result)
}, },
modifyPerpPosition: async (options: {
accountId: string
coin: BNCoin
changeDirection: boolean
}) => {
const msg: CreditManagerExecuteMsg = {
update_credit_account: {
account_id: options.accountId,
actions: [
...(options.changeDirection
? [
{
close_perp: {
denom: options.coin.denom,
},
},
{
open_perp: options.coin.toSignedCoin(),
},
]
: [
{
modify_perp: {
denom: options.coin.denom,
new_size: options.coin.amount.toString() as any,
},
},
]),
],
},
}
const cmContract = get().chainConfig.contracts.creditManager
const response = get().executeMsg({
messages: [generateExecutionMessage(get().address, cmContract, msg, [])],
})
get().setToast({
response,
options: {
action: 'modify-perp',
target: 'account',
message: `Modified position to a ${formatAmountWithSymbol(options.coin.abs().toCoin(), get().chainConfig.assets)} ${options.coin.amount.isNegative() ? 'short' : 'long'}`,
accountId: options.accountId,
changes: { deposits: [options.coin] },
},
})
return response.then((response) => !!response.result)
},
getPythVaas: async () => { getPythVaas: async () => {
const priceFeedIds = get() const priceFeedIds = get()
.chainConfig.assets.filter((asset) => !!asset.pythPriceFeedId) .chainConfig.assets.filter((asset) => !!asset.pythPriceFeedId)

View File

@ -44,4 +44,8 @@ export class BNCoin {
size: this.amount.toString(), size: this.amount.toString(),
} }
} }
abs() {
return BNCoin.fromDenomAndBigNumber(this.denom, this.amount.abs())
}
} }

View File

@ -28,374 +28,387 @@ export interface InstantiateMsg {
} }
export type ExecuteMsg = export type ExecuteMsg =
| { | {
create_credit_account: AccountKind create_credit_account: AccountKind
} }
| { | {
update_credit_account: { update_credit_account: {
account_id: string account_id: string
actions: Action[] actions: Action[]
} }
} }
| { | {
repay_from_wallet: { repay_from_wallet: {
account_id: string account_id: string
} }
} }
| { | {
update_config: { update_config: {
updates: ConfigUpdates updates: ConfigUpdates
} }
} }
| { | {
update_owner: OwnerUpdate update_owner: OwnerUpdate
} }
| { | {
update_nft_config: { update_nft_config: {
config?: NftConfigUpdates | null config?: NftConfigUpdates | null
ownership?: Action2 | null ownership?: Action2 | null
} }
} }
| { | {
callback: CallbackMsg callback: CallbackMsg
} }
export type AccountKind = 'default' | 'high_levered_strategy' export type AccountKind = 'default' | 'high_levered_strategy'
export type Action = export type Action =
| { | {
deposit: Coin deposit: Coin
} }
| { | {
withdraw: ActionCoin withdraw: ActionCoin
} }
| { | {
borrow: Coin borrow: Coin
} }
| { | {
lend: ActionCoin lend: ActionCoin
} }
| { | {
reclaim: ActionCoin reclaim: ActionCoin
} }
| { | {
claim_rewards: {} claim_rewards: {}
} }
| { | {
repay: { repay: {
coin: ActionCoin coin: ActionCoin
recipient_account_id?: string | null recipient_account_id?: string | null
} }
} }
| { | {
open_perp: { open_perp: {
denom: string denom: string
size: SignedDecimal size: SignedDecimal
} }
} }
| { | {
close_perp: { close_perp: {
denom: string denom: string
} }
} }
| { | {
enter_vault: { modify_perp: {
coin: ActionCoin denom: string
vault: VaultBaseForString new_size: SignedDecimal
} }
} }
| { | {
exit_vault: { enter_vault: {
amount: Uint128 coin: ActionCoin
vault: VaultBaseForString vault: VaultBaseForString
} }
} }
| { | {
request_vault_unlock: { exit_vault: {
amount: Uint128 amount: Uint128
vault: VaultBaseForString vault: VaultBaseForString
} }
} }
| { | {
exit_vault_unlocked: { request_vault_unlock: {
id: number amount: Uint128
vault: VaultBaseForString vault: VaultBaseForString
} }
} }
| { | {
liquidate: { exit_vault_unlocked: {
debt_coin: Coin id: number
liquidatee_account_id: string vault: VaultBaseForString
request: LiquidateRequestForVaultBaseForString }
} }
}
| { | {
swap_exact_in: { liquidate: {
coin_in: ActionCoin debt_coin: Coin
denom_out: string liquidatee_account_id: string
slippage: Decimal request: LiquidateRequestForVaultBaseForString
} }
} }
| { | {
provide_liquidity: { swap_exact_in: {
coins_in: ActionCoin[] coin_in: ActionCoin
lp_token_out: string denom_out: string
slippage: Decimal slippage: Decimal
} }
} }
| { | {
withdraw_liquidity: { provide_liquidity: {
lp_token: ActionCoin coins_in: ActionCoin[]
slippage: Decimal lp_token_out: string
} slippage: Decimal
} }
}
| { | {
refund_all_coin_balances: {} withdraw_liquidity: {
} lp_token: ActionCoin
slippage: Decimal
}
}
| {
refund_all_coin_balances: {}
}
export type ActionAmount = export type ActionAmount =
| 'account_balance' | 'account_balance'
| { | {
exact: Uint128 exact: Uint128
} }
export type LiquidateRequestForVaultBaseForString = export type LiquidateRequestForVaultBaseForString =
| { | {
deposit: string deposit: string
} }
| { | {
lend: string lend: string
} }
| { | {
vault: { vault: {
position_type: VaultPositionType position_type: VaultPositionType
request_vault: VaultBaseForString request_vault: VaultBaseForString
} }
} }
export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g'
export type AccountNftBaseForString = string export type AccountNftBaseForString = string
export type PerpsBaseForString = string export type PerpsBaseForString = string
export type OwnerUpdate = export type OwnerUpdate =
| { | {
propose_new_owner: { propose_new_owner: {
proposed: string proposed: string
} }
} }
| 'clear_proposed' | 'clear_proposed'
| 'accept_proposed' | 'accept_proposed'
| 'abolish_owner_role' | 'abolish_owner_role'
| { | {
set_emergency_owner: { set_emergency_owner: {
emergency_owner: string emergency_owner: string
} }
} }
| 'clear_emergency_owner' | 'clear_emergency_owner'
export type Action2 = export type Action2 =
| { | {
transfer_ownership: { transfer_ownership: {
expiry?: Expiration | null expiry?: Expiration | null
new_owner: string new_owner: string
} }
} }
| 'accept_ownership' | 'accept_ownership'
| 'renounce_ownership' | 'renounce_ownership'
export type Expiration = export type Expiration =
| { | {
at_height: number at_height: number
} }
| { | {
at_time: Timestamp at_time: Timestamp
} }
| { | {
never: {} never: {}
} }
export type Timestamp = Uint64 export type Timestamp = Uint64
export type Uint64 = string export type Uint64 = string
export type CallbackMsg = export type CallbackMsg =
| { | {
withdraw: { withdraw: {
account_id: string account_id: string
coin: ActionCoin coin: ActionCoin
recipient: Addr recipient: Addr
} }
} }
| { | {
borrow: { borrow: {
account_id: string account_id: string
coin: Coin coin: Coin
} }
} }
| { | {
repay: { repay: {
account_id: string account_id: string
coin: ActionCoin coin: ActionCoin
} }
} }
| { | {
repay_for_recipient: { repay_for_recipient: {
benefactor_account_id: string benefactor_account_id: string
coin: ActionCoin coin: ActionCoin
recipient_account_id: string recipient_account_id: string
} }
} }
| { | {
lend: { lend: {
account_id: string account_id: string
coin: ActionCoin coin: ActionCoin
} }
} }
| { | {
reclaim: { reclaim: {
account_id: string account_id: string
coin: ActionCoin coin: ActionCoin
} }
} }
| { | {
claim_rewards: { claim_rewards: {
account_id: string account_id: string
recipient: Addr recipient: Addr
} }
} }
| { | {
assert_max_ltv: { assert_max_ltv: {
account_id: string account_id: string
prev_health_state: HealthState prev_health_state: HealthState
} }
} }
| { | {
assert_deposit_caps: { assert_deposit_caps: {
denoms: string[] denoms: string[]
} }
} }
| { | {
open_perp: { open_perp: {
account_id: string account_id: string
denom: string denom: string
size: SignedDecimal size: SignedDecimal
} }
} }
| { | {
close_perp: { close_perp: {
account_id: string account_id: string
denom: string denom: string
} }
} }
| { | {
enter_vault: { modify_perp: {
account_id: string account_id: string
coin: ActionCoin denom: string
vault: VaultBaseForAddr new_size: SignedDecimal
} }
} }
| { | {
exit_vault: { enter_vault: {
account_id: string account_id: string
amount: Uint128 coin: ActionCoin
vault: VaultBaseForAddr vault: VaultBaseForAddr
} }
} }
| { | {
update_vault_coin_balance: { exit_vault: {
account_id: string account_id: string
previous_total_balance: Uint128 amount: Uint128
vault: VaultBaseForAddr vault: VaultBaseForAddr
} }
} }
| { | {
request_vault_unlock: { update_vault_coin_balance: {
account_id: string account_id: string
amount: Uint128 previous_total_balance: Uint128
vault: VaultBaseForAddr vault: VaultBaseForAddr
} }
} }
| { | {
exit_vault_unlocked: { request_vault_unlock: {
account_id: string account_id: string
position_id: number amount: Uint128
vault: VaultBaseForAddr vault: VaultBaseForAddr
} }
} }
| { | {
liquidate: { exit_vault_unlocked: {
debt_coin: Coin account_id: string
liquidatee_account_id: string position_id: number
liquidator_account_id: string vault: VaultBaseForAddr
request: LiquidateRequestForVaultBaseForAddr }
} }
}
| { | {
swap_exact_in: { liquidate: {
account_id: string debt_coin: Coin
coin_in: ActionCoin liquidatee_account_id: string
denom_out: string liquidator_account_id: string
slippage: Decimal request: LiquidateRequestForVaultBaseForAddr
} }
} }
| { | {
update_coin_balance: { swap_exact_in: {
account_id: string account_id: string
change: ChangeExpected coin_in: ActionCoin
previous_balance: Coin denom_out: string
} slippage: Decimal
} }
}
| { | {
update_coin_balance_after_vault_liquidation: { update_coin_balance: {
account_id: string account_id: string
previous_balance: Coin change: ChangeExpected
protocol_fee: Decimal previous_balance: Coin
} }
} }
| { | {
provide_liquidity: { update_coin_balance_after_vault_liquidation: {
account_id: string account_id: string
coins_in: ActionCoin[] previous_balance: Coin
lp_token_out: string protocol_fee: Decimal
slippage: Decimal }
} }
}
| { | {
withdraw_liquidity: { provide_liquidity: {
account_id: string account_id: string
lp_token: ActionCoin coins_in: ActionCoin[]
slippage: Decimal lp_token_out: string
} slippage: Decimal
} }
}
| { | {
refund_all_coin_balances: { withdraw_liquidity: {
account_id: string account_id: string
} lp_token: ActionCoin
} slippage: Decimal
}
}
| { | {
assert_hls_rules: { refund_all_coin_balances: {
account_id: string account_id: string
} }
} }
| { | {
remove_reentrancy_guard: {} assert_hls_rules: {
} account_id: string
}
}
| { | {
send_rewards_to_addr: { remove_reentrancy_guard: {}
account_id: string }
previous_balances: Coin[] | {
recipient: Addr send_rewards_to_addr: {
} account_id: string
} previous_balances: Coin[]
recipient: Addr
}
}
export type Addr = string export type Addr = string
export type HealthState = export type HealthState =
| 'healthy' | 'healthy'
| { | {
unhealthy: { unhealthy: {
max_ltv_health_factor: Decimal max_ltv_health_factor: Decimal
} }
} }
export type LiquidateRequestForVaultBaseForAddr = export type LiquidateRequestForVaultBaseForAddr =
| { | {
deposit: string deposit: string
} }
| { | {
lend: string lend: string
} }
| { | {
vault: { vault: {
position_type: VaultPositionType position_type: VaultPositionType
request_vault: VaultBaseForAddr request_vault: VaultBaseForAddr
} }
} }
export type ChangeExpected = 'increase' | 'decrease' export type ChangeExpected = 'increase' | 'decrease'
export interface Coin { export interface Coin {
amount: Uint128 amount: Uint128
@ -438,80 +451,80 @@ export interface VaultBaseForAddr {
} }
export type QueryMsg = export type QueryMsg =
| { | {
account_kind: { account_kind: {
account_id: string account_id: string
} }
} }
| { | {
accounts: { accounts: {
limit?: number | null limit?: number | null
owner: string owner: string
start_after?: string | null start_after?: string | null
} }
} }
| { | {
config: {} config: {}
} }
| { | {
vault_utilization: { vault_utilization: {
vault: VaultBaseForString vault: VaultBaseForString
} }
} }
| { | {
positions: { positions: {
account_id: string account_id: string
} }
} }
| { | {
all_coin_balances: { all_coin_balances: {
limit?: number | null limit?: number | null
start_after?: [string, string] | null start_after?: [string, string] | null
} }
} }
| { | {
all_debt_shares: { all_debt_shares: {
limit?: number | null limit?: number | null
start_after?: [string, string] | null start_after?: [string, string] | null
} }
} }
| { | {
total_debt_shares: string total_debt_shares: string
} }
| { | {
all_total_debt_shares: { all_total_debt_shares: {
limit?: number | null limit?: number | null
start_after?: string | null start_after?: string | null
} }
} }
| { | {
all_vault_positions: { all_vault_positions: {
limit?: number | null limit?: number | null
start_after?: [string, string] | null start_after?: [string, string] | null
} }
} }
| { | {
estimate_provide_liquidity: { estimate_provide_liquidity: {
coins_in: Coin[] coins_in: Coin[]
lp_token_out: string lp_token_out: string
} }
} }
| { | {
estimate_withdraw_liquidity: { estimate_withdraw_liquidity: {
lp_token: Coin lp_token: Coin
} }
} }
| { | {
vault_position_value: { vault_position_value: {
vault_position: VaultPosition vault_position: VaultPosition
} }
} }
export type VaultPositionAmount = export type VaultPositionAmount =
| { | {
unlocked: VaultAmount unlocked: VaultAmount
} }
| { | {
locking: LockingVaultAmount locking: LockingVaultAmount
} }
export type VaultAmount = string export type VaultAmount = string
export type VaultAmount1 = string export type VaultAmount1 = string
export type UnlockingPositions = VaultUnlockingPosition[] export type UnlockingPositions = VaultUnlockingPosition[]
@ -584,11 +597,11 @@ export type ArrayOfCoin = Coin[]
export type PnL = export type PnL =
| 'break_even' | 'break_even'
| { | {
profit: Coin profit: Coin
} }
| { | {
loss: Coin loss: Coin
} }
export interface Positions { export interface Positions {
account_id: string account_id: string
debts: DebtAmount[] debts: DebtAmount[]
@ -605,11 +618,21 @@ export interface DebtAmount {
export interface PerpPosition { export interface PerpPosition {
base_denom: string base_denom: string
closing_fee_rate: Decimal closing_fee_rate: Decimal
current_exec_price: Decimal
current_price: Decimal current_price: Decimal
denom: string denom: string
entry_exec_price: Decimal
entry_price: Decimal entry_price: Decimal
pnl: PositionPnl realised_pnl: RealizedPnlAmounts
size: SignedDecimal size: SignedDecimal
unrealised_pnl: PositionPnl
}
export interface RealizedPnlAmounts {
accrued_funding: SignedDecimal
closing_fee: SignedDecimal
opening_fee: SignedDecimal
pnl: SignedDecimal
price_pnl: SignedDecimal
} }
export interface PositionPnl { export interface PositionPnl {
coins: PnlCoins coins: PnlCoins

View File

@ -22,63 +22,70 @@ export interface InstantiateMsg {
} }
export type ExecuteMsg = export type ExecuteMsg =
| { | {
update_owner: OwnerUpdate update_owner: OwnerUpdate
} }
| { | {
init_denom: { init_denom: {
denom: string denom: string
max_funding_velocity: Decimal max_funding_velocity: Decimal
skew_scale: Decimal skew_scale: Decimal
} }
} }
| { | {
enable_denom: { enable_denom: {
denom: string denom: string
} }
} }
| { | {
disable_denom: { disable_denom: {
denom: string denom: string
} }
} }
| { | {
deposit: {} deposit: {}
} }
| { | {
unlock: { unlock: {
shares: Uint128 shares: Uint128
} }
} }
| { | {
withdraw: {} withdraw: {}
} }
| { | {
open_position: { open_position: {
account_id: string account_id: string
denom: string denom: string
size: SignedDecimal size: SignedDecimal
} }
} }
| { | {
close_position: { close_position: {
account_id: string account_id: string
denom: string denom: string
} }
} }
| {
modify_position: {
account_id: string
denom: string
new_size: SignedDecimal
}
}
export type OwnerUpdate = export type OwnerUpdate =
| { | {
propose_new_owner: { propose_new_owner: {
proposed: string proposed: string
} }
} }
| 'clear_proposed' | 'clear_proposed'
| 'accept_proposed' | 'accept_proposed'
| 'abolish_owner_role' | 'abolish_owner_role'
| { | {
set_emergency_owner: { set_emergency_owner: {
emergency_owner: string emergency_owner: string
} }
} }
| 'clear_emergency_owner' | 'clear_emergency_owner'
export interface SignedDecimal { export interface SignedDecimal {
abs: Decimal abs: Decimal
@ -87,72 +94,86 @@ export interface SignedDecimal {
} }
export type QueryMsg = export type QueryMsg =
| { | {
owner: {} owner: {}
} }
| { | {
config: {} config: {}
} }
| { | {
vault_state: {} vault_state: {}
} }
| { | {
denom_state: { denom_state: {
denom: string denom: string
} }
} }
| { | {
perp_denom_state: { perp_denom_state: {
denom: string denom: string
} }
} }
| { | {
denom_states: { denom_states: {
limit?: number | null limit?: number | null
start_after?: string | null start_after?: string | null
} }
} }
| { | {
deposit: { deposit: {
depositor: string depositor: string
} }
} }
| { | {
deposits: { deposits: {
limit?: number | null limit?: number | null
start_after?: string | null start_after?: string | null
} }
} }
| { | {
unlocks: { unlocks: {
depositor: string depositor: string
} }
} }
| { | {
position: { position: {
account_id: string account_id: string
denom: string denom: string
} }
} }
| { | {
positions: { positions: {
limit?: number | null limit?: number | null
start_after?: [string, string] | null start_after?: [string, string] | null
} }
} }
| { | {
positions_by_account: { positions_by_account: {
account_id: string account_id: string
} }
} }
| { | {
total_pnl: {} total_pnl: {}
} }
| { | {
opening_fee: { opening_fee: {
denom: string denom: string
size: SignedDecimal size: SignedDecimal
} }
} }
| {
denom_accounting: {
denom: string
}
}
| {
total_accounting: {}
}
| {
denom_realized_pnl_for_account: {
account_id: string
denom: string
}
}
export interface ConfigForString { export interface ConfigForString {
base_denom: string base_denom: string
closing_fee_rate: Decimal closing_fee_rate: Decimal
@ -164,6 +185,31 @@ export interface ConfigForString {
oracle: OracleBaseForString oracle: OracleBaseForString
params: ParamsBaseForString params: ParamsBaseForString
} }
export interface Accounting {
balance: Balance
cash_flow: CashFlow
withdrawal_balance: Balance
}
export interface Balance {
accrued_funding: SignedDecimal
closing_fee: SignedDecimal
opening_fee: SignedDecimal
price_pnl: SignedDecimal
total: SignedDecimal
}
export interface CashFlow {
accrued_funding: SignedDecimal
closing_fee: SignedDecimal
opening_fee: SignedDecimal
price_pnl: SignedDecimal
}
export interface RealizedPnlAmounts {
accrued_funding: SignedDecimal
closing_fee: SignedDecimal
opening_fee: SignedDecimal
pnl: SignedDecimal
price_pnl: SignedDecimal
}
export interface DenomStateResponse { export interface DenomStateResponse {
denom: string denom: string
enabled: boolean enabled: boolean
@ -203,24 +249,27 @@ export interface OwnerResponse {
export interface PerpDenomState { export interface PerpDenomState {
denom: string denom: string
enabled: boolean enabled: boolean
long_oi: Decimal
pnl_values: DenomPnlValues pnl_values: DenomPnlValues
rate: SignedDecimal rate: SignedDecimal
short_oi: Decimal
total_entry_cost: SignedDecimal total_entry_cost: SignedDecimal
total_entry_funding: SignedDecimal total_entry_funding: SignedDecimal
} }
export interface DenomPnlValues { export interface DenomPnlValues {
accrued_funding: SignedDecimal accrued_funding: SignedDecimal
closing_fees: SignedDecimal
pnl: SignedDecimal pnl: SignedDecimal
price_pnl: SignedDecimal price_pnl: SignedDecimal
} }
export type PnL = export type PnL =
| 'break_even' | 'break_even'
| { | {
profit: Coin profit: Coin
} }
| { | {
loss: Coin loss: Coin
} }
export interface PositionResponse { export interface PositionResponse {
account_id: string account_id: string
position: PerpPosition position: PerpPosition
@ -228,11 +277,14 @@ export interface PositionResponse {
export interface PerpPosition { export interface PerpPosition {
base_denom: string base_denom: string
closing_fee_rate: Decimal closing_fee_rate: Decimal
current_exec_price: Decimal
current_price: Decimal current_price: Decimal
denom: string denom: string
entry_exec_price: Decimal
entry_price: Decimal entry_price: Decimal
pnl: PositionPnl realised_pnl: RealizedPnlAmounts
size: SignedDecimal size: SignedDecimal
unrealised_pnl: PositionPnl
} }
export interface PositionPnl { export interface PositionPnl {
coins: PnlCoins coins: PnlCoins

View File

@ -11,7 +11,6 @@ interface PerpsPosition {
baseDenom: string baseDenom: string
tradeDirection: TradeDirection tradeDirection: TradeDirection
amount: BigNumber amount: BigNumber
closingFee: BNCoin
pnl: PerpsPnL pnl: PerpsPnL
entryPrice: BigNumber entryPrice: BigNumber
} }
@ -34,3 +33,16 @@ interface PerpsPnLCoins {
net: BNCoin net: BNCoin
price: BNCoin price: BNCoin
} }
interface PerpsPnL {
net: BNCoin
realized: PerpsPnLCoins
unrealized: PerpsPnLCoins
}
interface PerpsPnLCoins {
fees: BNCoin
funding: BNCoin
net: BNCoin
price: BNCoin
}

View File

@ -72,6 +72,7 @@ interface HandleResponseProps {
| 'hls-staking' | 'hls-staking'
| 'open-perp' | 'open-perp'
| 'close-perp' | 'close-perp'
| 'modify-perp'
lend?: boolean lend?: boolean
accountId?: string accountId?: string
changes?: { changes?: {
@ -123,6 +124,11 @@ interface BroadcastSlice {
lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean> lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean>
closePerpPosition: (options: { accountId: string; denom: string }) => Promise<boolean> closePerpPosition: (options: { accountId: string; denom: string }) => Promise<boolean>
openPerpPosition: (options: { accountId: string; coin: BNCoin }) => Promise<boolean> openPerpPosition: (options: { accountId: string; coin: BNCoin }) => Promise<boolean>
modifyPerpPosition: (options: {
accountId: string
coin: BNCoin
changeDirection: boolean
}) => Promise<boolean>
reclaim: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean> reclaim: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean>
repay: (options: { repay: (options: {
accountId: string accountId: string

View File

@ -248,7 +248,6 @@ export function cloneAccount(account: Account): Account {
perps: account.perps.map((perpPosition) => ({ perps: account.perps.map((perpPosition) => ({
...perpPosition, ...perpPosition,
amount: perpPosition.amount, amount: perpPosition.amount,
closingFee: perpPosition.closingFee,
pnl: perpPosition.pnl, pnl: perpPosition.pnl,
entryPrice: perpPosition.entryPrice, entryPrice: perpPosition.entryPrice,
tradeDirection: perpPosition.tradeDirection, tradeDirection: perpPosition.tradeDirection,

View File

@ -108,7 +108,7 @@ export function resolvePerpsPositions(
perpPositions: Positions['perps'], perpPositions: Positions['perps'],
prices: BNCoin[], prices: BNCoin[],
): PerpsPosition[] { ): PerpsPosition[] {
if (!perpPositions) return [] if (!perpPositions.length) return []
const basePrice = const basePrice =
prices.find((price) => price.denom === perpPositions[0].base_denom)?.amount ?? BN_ZERO prices.find((price) => price.denom === perpPositions[0].base_denom)?.amount ?? BN_ZERO
@ -116,36 +116,52 @@ export function resolvePerpsPositions(
return { return {
denom: position.denom, denom: position.denom,
baseDenom: position.base_denom, baseDenom: position.base_denom,
amount: BN(position.size as any).abs(), amount: BN(position.size as any), // Amount is negative for SHORT positions
tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long', tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long',
closingFee: BNCoin.fromCoin(position.pnl.coins.closing_fee), // closingFee: BNCoin.fromCoin(position.pnl.coins.closing_fee),
pnl: { pnl: {
net: BNCoin.fromDenomAndBigNumber( net: BNCoin.fromDenomAndBigNumber(
position.base_denom, position.base_denom,
BN(position.pnl.values.pnl as any).plus(BN_ZERO), BN(position.unrealised_pnl.values.pnl as any)
.div(basePrice)
.plus(position.realised_pnl.pnl as any),
), ),
realized: { realized: {
net: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO), net: BNCoin.fromDenomAndBigNumber(
price: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO), position.base_denom,
funding: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO), BN(position.realised_pnl.pnl as any),
fees: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO.times(-1)), ),
price: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.realised_pnl.price_pnl as any),
),
funding: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.realised_pnl.accrued_funding as any),
),
fees: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.realised_pnl.closing_fee as any).plus(
position.realised_pnl.opening_fee as any,
),
),
}, },
unrealized: { unrealized: {
net: BNCoin.fromDenomAndBigNumber( net: BNCoin.fromDenomAndBigNumber(
position.base_denom, position.base_denom,
BN(position.pnl.values.pnl as any), BN(position.unrealised_pnl.values.pnl as any).div(basePrice),
), ),
price: BNCoin.fromDenomAndBigNumber( price: BNCoin.fromDenomAndBigNumber(
position.base_denom, position.base_denom,
BN(position.pnl.values.price_pnl as any), BN(position.unrealised_pnl.values.price_pnl as any).div(basePrice),
), ),
funding: BNCoin.fromDenomAndBigNumber( funding: BNCoin.fromDenomAndBigNumber(
position.base_denom, position.base_denom,
BN(position.pnl.values.accrued_funding as any), BN(position.unrealised_pnl.values.accrued_funding as any).div(basePrice),
), ),
fees: BNCoin.fromDenomAndBigNumber( fees: BNCoin.fromDenomAndBigNumber(
position.base_denom, position.base_denom,
BN(position.pnl.values.closing_fee as any).times(-1), BN(position.unrealised_pnl.values.closing_fee as any).div(basePrice),
), ),
}, },
}, },