Skip to content

Commit

Permalink
re-enable spend max feature (#1698)
Browse files Browse the repository at this point in the history
* reenable spend max in frontend

* max send invariants

* reenable spend max in planner

* lintingg

* refactor assertion checks

* fix swaps and state updates

* modify privacy warning

* Refactoring spend max (#1760)

* refactor to use zustand and remove extraneous state

* more refactor and linting

* fix bug with non max amounts

* forgot to add unit tests

* error message

* linting

* changeset

* bump wasm deps to v0.80.5

* extra safety checks in wasm planner

* update lockfile

* split wasm code

* refactoring assertions

* assertion cleanup

* fix import

* gabe's suggestion's

* revert

* linting galore

* extra extra extra validation for sanity checking

* Suggestions (#1867)

* changeset for updates packages

* delete outdated changeset

---------

Co-authored-by: Gabe Rodriguez <[email protected]>
  • Loading branch information
TalDerei and grod220 authored Oct 30, 2024
1 parent 7c1d4e7 commit b5d2922
Show file tree
Hide file tree
Showing 15 changed files with 780 additions and 71 deletions.
8 changes: 8 additions & 0 deletions .changeset/strange-flowers-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@penumbra-zone/services': minor
'@penumbra-zone/storage': minor
'minifront': minor
'@penumbra-zone/types': minor
---

send max feature
3 changes: 3 additions & 0 deletions apps/minifront/src/components/send/send-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import { transferableBalancesResponsesSelector } from '../../../state/send/helpe
import { useRefreshFee } from '../../v2/transfer-layout/send-page/use-refresh-fee';

export const SendForm = () => {
// Retrieve the staking token metadata and gas prices from the zustand
const stakingTokenMetadata = useStakingTokenMetadata();

const transferableBalancesResponses = useBalancesResponses({
select: transferableBalancesResponsesSelector,
});

const {
selection,
amount,
Expand Down
66 changes: 20 additions & 46 deletions apps/minifront/src/components/shared/non-native-fee-warning.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { ReactNode } from 'react';

import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';
import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { getAssetIdFromValueView } from '@penumbra-zone/getters/value-view';
import { useStakingTokenMetadata } from '../../state/shared';
import { ReactNode, useCallback, useEffect, useState } from 'react';
import {
getAddressIndex,
getAmount,
getAssetIdFromBalancesResponse,
} from '@penumbra-zone/getters/balances-response';
import { ViewService } from '@penumbra-zone/protobuf';
import { GasPrices } from '@penumbra-zone/protobuf/penumbra/core/component/fee/v1/fee_pb';
import { getAssetId } from '@penumbra-zone/getters/metadata';
import { penumbra } from '../../penumbra';
import { useGasPrices, useStakingTokenMetadata } from '../../state/shared.ts';
import { hasStakingToken } from '../../fetchers/gas-prices.ts';

const hasTokenBalance = ({
const hasRelevantAltTokenBalance = ({
source,
balancesResponses = [],
gasPrices,
Expand All @@ -29,16 +27,8 @@ const hasTokenBalance = ({
return false;
}

// Finds the UM token in the user's account balances
const hasStakingToken = balancesResponses.some(
asset =>
getAssetIdFromValueView
.optional(asset.balanceView)
?.equals(getAssetId.optional(stakingAssetMetadata)) &&
getAddressIndex.optional(asset)?.account === account,
);

if (hasStakingToken) {
const hasUmInAccount = hasStakingToken(balancesResponses, stakingAssetMetadata, source);
if (hasUmInAccount) {
return false;
}

Expand All @@ -61,47 +51,32 @@ const hasTokenBalance = ({
return hasAltTokens;
};

const useGasPrices = () => {
const [prices, setPrices] = useState<GasPrices[]>([]);

const fetchGasPrices = useCallback(async () => {
const res = await penumbra.service(ViewService).gasPrices({});
setPrices(res.altGasPrices);
}, []);

useEffect(() => {
void fetchGasPrices();
}, [fetchGasPrices]);

return prices;
};

/**
* Renders a non-native fee warning if
* 1. the user does not have any balance (in the selected account) of the staking token to use for fees
* 2. the user does not have sufficient balances in alternative tokens to cover the fees
*/
export const NonNativeFeeWarning = ({
balancesResponses = [],
amount,
balancesResponses,
source,
wrap = children => children,
}: {
/**
* The user's balances that are relevant to this transaction, from which
* `<NonNativeFeeWarning />` will determine whether to render.
*/
balancesResponses?: BalancesResponse[];
/**
* The amount that the user is putting into this transaction, which will help
* determine whether the warning should render.
*/
amount: number;
/**
* The user's balances that are relevant to this transaction, from which
* `<NonNativeFeeWarning />` will determine whether to render.
*/
balancesResponses: BalancesResponse[] | undefined;
/**
* A source token – helps determine whether the user has UM token
* in the same account as `source` to use for fees.
*/
source?: BalancesResponse;
source: BalancesResponse | undefined;
/*
* Since this component determines for itself whether to render, a parent
* component can't optionally render wrapper markup depending on whether this
Expand All @@ -121,17 +96,16 @@ export const NonNativeFeeWarning = ({
*/
wrap?: (children: ReactNode) => ReactNode;
}) => {
const gasPrices = useGasPrices();
const { data } = useGasPrices();
const stakingTokenMetadata = useStakingTokenMetadata();
const shouldRender =
!!amount &&
hasTokenBalance({
hasRelevantAltTokenBalance({
source,
balancesResponses,
gasPrices,
balancesResponses: balancesResponses ?? [],
gasPrices: data ?? [],
stakingAssetMetadata: stakingTokenMetadata.data,
});

if (!shouldRender) {
return null;
}
Expand All @@ -140,8 +114,8 @@ export const NonNativeFeeWarning = ({
<div className='rounded border border-yellow-500 p-4 text-yellow-500'>
<strong>Privacy Warning:</strong>
<span className='block'>
Using non-native tokens for transaction fees may pose a privacy risk. It is recommended to
use the native token (UM) for better privacy and security.
You are using an alternative token for transaction fees, which may pose a privacy risk. It
is recommended to use the native token (UM) for better privacy and security.
</span>
</div>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { zeroValueView } from '../../../utils/zero-value-view';
import { isValidAmount } from '../../../state/helpers';
import { NonNativeFeeWarning } from '../../shared/non-native-fee-warning';
import { NumberInput } from '../../shared/number-input';
import { useBalancesResponses, useAssets } from '../../../state/shared';
import { useAssets, useBalancesResponses } from '../../../state/shared';
import { getBalanceByMatchingMetadataAndAddressIndex } from '../../../state/swap/getters';
import {
swappableAssetsSelector,
Expand Down Expand Up @@ -141,12 +141,13 @@ export const TokenSwapInput = () => {
</div>

<NonNativeFeeWarning
balancesResponses={balancesResponses?.data}
amount={Number(amount)}
balancesResponses={balancesResponses?.data}
source={assetIn}
wrap={children => (
<>
{/* This div adds an empty line */} <div className='h-4' />
{/* This div adds an empty line */}
<div className='h-4' />
{children}
</>
)}
Expand Down
38 changes: 38 additions & 0 deletions apps/minifront/src/fetchers/gas-prices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ViewService } from '@penumbra-zone/protobuf';
import { GasPrices } from '@penumbra-zone/protobuf/penumbra/core/component/fee/v1/fee_pb';
import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';
import { getAssetIdFromValueView } from '@penumbra-zone/getters/value-view';
import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { getAddressIndex } from '@penumbra-zone/getters/balances-response';
import { getAssetId } from '@penumbra-zone/getters/metadata';
import { penumbra } from '../penumbra';

// Fetches gas prices
export const getGasPrices = async (): Promise<GasPrices[]> => {
const res = await penumbra.service(ViewService).gasPrices({});
return res.altGasPrices;
};

// Determines if the user has UM token in their account balances
export const hasStakingToken = (
balancesResponses: BalancesResponse[] | undefined,
stakingAssetMetadata: Metadata | undefined,
source: BalancesResponse | undefined,
): boolean => {
if (!balancesResponses || !stakingAssetMetadata || !source) {
return false;
}

const account = getAddressIndex.optional(source)?.account;
if (typeof account === 'undefined') {
return false;
}

return balancesResponses.some(
asset =>
getAssetIdFromValueView
.optional(asset.balanceView)
?.equals(getAssetId.optional(stakingAssetMetadata)) &&
getAddressIndex.optional(asset)?.account === account,
);
};
Loading

0 comments on commit b5d2922

Please sign in to comment.