Skip to content

Commit

Permalink
feat(ui): #2053: implement PositionClose action view
Browse files Browse the repository at this point in the history
  • Loading branch information
VanishMax committed Feb 21, 2025
1 parent 6f2d7cb commit c8ff315
Show file tree
Hide file tree
Showing 17 changed files with 159 additions and 115 deletions.
26 changes: 13 additions & 13 deletions packages/ui/src/ActionView/action-view.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { FC } from 'react';
import { ActionView as ActionViewMessage } from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import { ActionViewType, ActionViewValueType, GetMetadataByAssetId } from './types';
import {
ActionViewBaseProps,
ActionViewType,
ActionViewValueType,
GetMetadataByAssetId,
} from './types';
import { UnknownAction } from './actions/unknown';

import { SpendAction } from './actions/spend';
import { OutputAction } from './actions/output';
import { SwapAction } from './actions/swap';
import { SwapClaimAction } from './actions/swap-claim';
import { SwapAction } from './swap/swap';
import { SwapClaimAction } from './swap/swap-claim';
import { DelegateAction } from './actions/delegate';
import { DelegatorVoteAction } from './actions/delegator-vote';
import { IbcRelayAction } from './actions/ibc-relay';
import { Ics20WithdrawalAction } from './actions/ics-20-withdrawal';
import { UndelegateAction } from './actions/undelegate';
import { UndelegateClaimAction } from './actions/undelegate-claim';
import { PositionCloseAction } from './actions/position-close';
import { PositionOpenAction } from './actions/position-open';
import { PositionRewardClaimAction } from './actions/position-reward-claim';
import { PositionWithdrawAction } from './actions/position-withdraw';
import { PositionCloseAction } from './position/position-close';
import { PositionOpenAction } from './position/position-open';
import { PositionRewardClaimAction } from './position/position-reward-claim';
import { PositionWithdrawAction } from './position/position-withdraw';
import { ProposalDepositClaimAction } from './actions/proposal-deposit-claim';
import { ProposalSubmitAction } from './actions/proposal-submit';
import { ProposalWithdrawAction } from './actions/proposal-withdraw';
Expand All @@ -29,17 +34,12 @@ import { CommunityPoolDepositAction } from './actions/community-pool-deposit';
import { CommunityPoolOutputAction } from './actions/community-pool-output';
import { CommunityPoolSpendAction } from './actions/community-pool-spend';

export interface ActionViewProps {
export interface ActionViewProps extends ActionViewBaseProps {
/**
* The `ActionViewMessage` protobuf describing an action within a transaction in Penumbra.
* Can be one of multiple types: Spend, Output, Swap, SwapClaim, etc.
*/
action: ActionViewMessage;
/**
* A helper function that is needed for better fees calculation.
* Can be omitted, but it generally improves the rendering logic, especially for opaque views.
*/
getMetadataByAssetId?: GetMetadataByAssetId;
}

const componentMap = {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/ActionView/actions/output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { getNote } from '@penumbra-zone/getters/output-view';
import { getAddress } from '@penumbra-zone/getters/note-view';
import { Density } from '../../Density';
import { useDensity } from '../../utils/density';
import { ActionWrapper } from './wrapper';
import { ValueViewComponent } from '../../ValueView';
import { AddressViewComponent } from '../../AddressView';
import { ActionWrapper } from '../shared/wrapper';

export interface OutputActionProps {
value: OutputView;
Expand Down
11 changes: 0 additions & 11 deletions packages/ui/src/ActionView/actions/position-close.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/ui/src/ActionView/actions/spend.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { SpendView } from '@penumbra-zone/protobuf/penumbra/core/component/shielded_pool/v1/shielded_pool_pb';
import { Density } from '../../Density';
import { ActionWrapper } from './wrapper';
import { ValueViewComponent } from '../../ValueView';
import { AddressViewComponent } from '../../AddressView';
import { useDensity } from '../../utils/density';
import { ActionWrapper } from '../shared/wrapper';

export interface SpendActionProps {
value: SpendView;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/ActionView/actions/unknown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Text } from '../../Text';
import { ActionWrapper } from './wrapper';
import { ActionWrapper } from '../shared/wrapper';

export interface UnknownActionProps {
label: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/ActionView/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SwapClaimActionOpaque,
registry,
PositionOpenAction,
PositionCloseAction,
} from '../utils/bufs';

const OPTIONS: Record<string, ActionViewMessage> = {
Expand All @@ -21,6 +22,7 @@ const OPTIONS: Record<string, ActionViewMessage> = {
Swap: SwapAction,
SwapClaim: SwapClaimAction,
PositionOpen: PositionOpenAction,
PositionClose: PositionCloseAction,
'Opaque: Spend': SpendActionOpaque,
'Opaque: Output': OutputActionOpaque,
'Opaque: Swap': SwapActionOpaque,
Expand Down
30 changes: 30 additions & 0 deletions packages/ui/src/ActionView/position/position-close.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PositionClose } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { ActionViewBaseProps, GetMetadataByAssetId } from '../types';
import { ActionWrapper } from '../shared/wrapper';
import { ActionRow } from '../shared/action-row';
import { bech32mPositionId } from '@penumbra-zone/bech32m/plpid';
import { shorten } from '@penumbra-zone/types/string';

export interface PositionCloseActionProps extends ActionViewBaseProps {
value: PositionClose;
getMetadataByAssetId?: GetMetadataByAssetId;
}

export const PositionCloseAction = ({ value }: PositionCloseActionProps) => {
return (
<ActionWrapper
title='Position Close'
infoRows={
<>
{value.positionId && (
<ActionRow
label='Position ID'
info={shorten(bech32mPositionId(value.positionId), 8)}
copyText={bech32mPositionId(value.positionId)}
/>
)}
</>
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { AssetGroup } from '../../AssetIcon';
import { Density } from '../../Density';
import { Pill } from '../../Pill';
import { Text } from '../../Text';
import { GetMetadataByAssetId } from '../types';
import { ActionWrapper } from './wrapper';
import { ActionRow } from './action-row';
import { ActionViewBaseProps } from '../types';
import { ActionWrapper } from '../shared/wrapper';
import { ActionRow } from '../shared/action-row';

export interface PositionOpenActionProps {
export interface PositionOpenActionProps extends ActionViewBaseProps {
value: PositionOpen;
getMetadataByAssetId?: GetMetadataByAssetId;
}

export const PositionOpenAction = ({ value, getMetadataByAssetId }: PositionOpenActionProps) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PositionRewardClaim } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';
import { UnknownAction } from '../actions/unknown';

export interface PositionRewardClaimActionProps {
value: PositionRewardClaim;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PositionWithdraw } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';
import { UnknownAction } from '../actions/unknown';

export interface PositionWithdrawActionProps {
value: PositionWithdraw;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cn from 'clsx';
import { ReactNode } from 'react';
import { Text } from '../../Text';
import { IncognitoIcon } from './incognito-icon';
import { IncognitoIcon } from '../actions/incognito-icon';
import { Density, useDensity } from '../../utils/density';

export interface ActionWrapperProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { shorten } from '@penumbra-zone/types/string';
import { uint8ArrayToHex } from '@penumbra-zone/types/hex';
import { getAmount, getMetadata } from '@penumbra-zone/getters/value-view';
import { SwapClaimView } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import {
getOutput1Value,
getOutput2Value,
Expand All @@ -14,16 +15,13 @@ import {
import { Density } from '../../Density';
import { ValueViewComponent } from '../../ValueView';
import { useDensity } from '../../utils/density';
import { ActionRow } from './action-row';
import { ActionWrapper } from './wrapper';
import { parseSwapFees } from './swap';
import { GetMetadataByAssetId } from '../types';
import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { ActionRow } from '../shared/action-row';
import { ActionWrapper } from '../shared/wrapper';
import { parseSwapFees } from './utils';
import { ActionViewBaseProps } from '../types';

export interface SwapClaimActionProps {
export interface SwapClaimActionProps extends ActionViewBaseProps {
value: SwapClaimView;
/** A helper needed to calculate the SwapClaim fees */
getMetadataByAssetId?: GetMetadataByAssetId;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,95 +1,29 @@
import { useMemo } from 'react';
import { ArrowRight } from 'lucide-react';
import { SwapView } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { Metadata, ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { Fee } from '@penumbra-zone/protobuf/penumbra/core/component/fee/v1/fee_pb';
import {
getAsset1Metadata,
getAsset2Metadata,
getClaimFeeFromSwapView,
getClaimTx,
} from '@penumbra-zone/getters/swap-view';
import { getOneWaySwapValues, isOneWaySwap } from '@penumbra-zone/types/swap';
import { getAmount, getMetadata } from '@penumbra-zone/getters/value-view';
import { getAmount } from '@penumbra-zone/getters/value-view';
import { isZero } from '@penumbra-zone/types/amount';
import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view';
import { uint8ArrayToHex } from '@penumbra-zone/types/hex';
import { shorten } from '@penumbra-zone/types/string';
import { GetMetadataByAssetId } from '../types';
import { ActionViewBaseProps } from '../types';
import { ValueViewComponent } from '../../ValueView';
import { useDensity } from '../../utils/density';
import { Density } from '../../Density';
import { ActionWrapper } from './wrapper';
import { ActionRow } from './action-row';
import { ActionWrapper } from '../shared/wrapper';
import { ActionRow } from '../shared/action-row';
import { parseSwapFees, renderAmount } from './utils';

export interface SwapActionProps {
export interface SwapActionProps extends ActionViewBaseProps {
value: SwapView;
/** A helper needed to calculate the Swap fees */
getMetadataByAssetId: GetMetadataByAssetId;
}

const renderAmount = (value?: ValueView) => {
if (!value) {
return undefined;
}
const symbol = getMetadata.optional(value)?.symbol;
return symbol ? `${getFormattedAmtFromValueView(value)} ${symbol}` : undefined;
};

/**
* For Swap and SwapClaim actions, fees contain only the assetId and amount. This function
* calculates a Metadata from this assetId. It firstly tries to get the info from the action itself,
* and if it fails, it takes the Metadata from the registry (or ViewService, if passed).
*/
export const parseSwapFees = (
fee?: Fee,
asset1?: Metadata,
asset2?: Metadata,
getMetadataByAssetId?: SwapActionProps['getMetadataByAssetId'],
): string | undefined => {
if (!fee) {
return undefined;
}

let metadata: Metadata | undefined = undefined;
if (fee.assetId?.equals(asset1?.penumbraAssetId)) {
metadata = asset1;
}
if (fee.assetId?.equals(asset2?.penumbraAssetId)) {
metadata = asset1;
}

if (!metadata && fee.assetId && getMetadataByAssetId) {
metadata = getMetadataByAssetId(fee.assetId);
}

if (metadata) {
return renderAmount(
new ValueView({
valueView: {
case: 'knownAssetId',
value: {
metadata,
amount: fee.amount,
},
},
}),
);
}

return renderAmount(
new ValueView({
valueView: {
case: 'unknownAssetId',
value: {
assetId: fee.assetId,
amount: fee.amount,
},
},
}),
);
};

export const SwapAction = ({ value, getMetadataByAssetId }: SwapActionProps) => {
const density = useDensity();

Expand Down
67 changes: 67 additions & 0 deletions packages/ui/src/ActionView/swap/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { getMetadata } from '@penumbra-zone/getters/value-view';
import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view';
import { Metadata, ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { Fee } from '@penumbra-zone/protobuf/penumbra/core/component/fee/v1/fee_pb';
import type { SwapActionProps } from './swap';

export const renderAmount = (value?: ValueView) => {
if (!value) {
return undefined;
}
const symbol = getMetadata.optional(value)?.symbol;
return symbol ? `${getFormattedAmtFromValueView(value)} ${symbol}` : undefined;
};

/**
* For Swap and SwapClaim actions, fees contain only the assetId and amount. This function
* calculates a Metadata from this assetId. It firstly tries to get the info from the action itself,
* and if it fails, it takes the Metadata from the registry (or ViewService, if passed).
*/
export const parseSwapFees = (
fee?: Fee,
asset1?: Metadata,
asset2?: Metadata,
getMetadataByAssetId?: SwapActionProps['getMetadataByAssetId'],
): string | undefined => {
if (!fee) {
return undefined;
}

let metadata: Metadata | undefined = undefined;
if (fee.assetId?.equals(asset1?.penumbraAssetId)) {
metadata = asset1;
}
if (fee.assetId?.equals(asset2?.penumbraAssetId)) {
metadata = asset1;
}

if (!metadata && fee.assetId && getMetadataByAssetId) {
metadata = getMetadataByAssetId(fee.assetId);
}

if (metadata) {
return renderAmount(
new ValueView({
valueView: {
case: 'knownAssetId',
value: {
metadata,
amount: fee.amount,
},
},
}),
);
}

return renderAmount(
new ValueView({
valueView: {
case: 'unknownAssetId',
value: {
assetId: fee.assetId,
amount: fee.amount,
},
},
}),
);
};
9 changes: 9 additions & 0 deletions packages/ui/src/ActionView/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ export type ActionViewType = Exclude<ActionView['actionView']['case'], undefined
export type ActionViewValueType = Exclude<ActionView['actionView']['value'], undefined>;

export type GetMetadataByAssetId = (assetId: AssetId) => Metadata | undefined;

export interface ActionViewBaseProps {
/**
* A helper function that is needed to match action assets with their metadata.
* Can be omitted, but it generally improves the rendering logic, especially for opaque views.
* If omitted, some assets may be rendered as unknown.
*/
getMetadataByAssetId?: GetMetadataByAssetId;
}
Loading

0 comments on commit c8ff315

Please sign in to comment.