Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook up SendFunds page #571

Merged
merged 12 commits into from
Nov 21, 2024
13 changes: 5 additions & 8 deletions packages/browser-wallet/src/popup/popupX/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,8 @@ export const relativeRoutes = {
hideBackArrow: true,
showAccountSelector: true,
},
send: {
path: 'send',
confirmation: {
path: 'confirmation',
config: {
backTitle: 'to Send Funds form',
},
},
sendFunds: {
path: 'account/:account/send-funds',
},
receive: {
path: 'receive',
Expand Down Expand Up @@ -347,6 +341,9 @@ export const transactionDetailsRoute = (account: AccountAddress.Type, tx: Transa
export const submittedTransactionRoute = (tx: TransactionHash.Type) =>
generatePath(absoluteRoutes.home.submittedTransaction.path, { transactionHash: TransactionHash.toHexString(tx) });

export const sendFundsRoute = (account: AccountAddress.Type) =>
generatePath(absoluteRoutes.home.sendFunds.path, { account: account.address });

/**
* Given two absolute routes, returns the relative route between them.
* Note: fromPath should be a prefix of toPath.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function DelegationResult() {
};
const nav = useNavigate();
const { t } = useTranslation('x', { keyPrefix: 'earn.delegator' });
const getCost = useGetTransactionFee(AccountTransactionType.ConfigureDelegation);
const getCost = useGetTransactionFee();
const accountInfo = ensureDefined(useSelectedAccountInfo(), 'No account selected');

const parametersV1 = useBlockChainParametersAboveV0();
Expand Down Expand Up @@ -75,7 +75,7 @@ export default function DelegationResult() {
return <Navigate to=".." />;
}

const fee = getCost(state.payload);
const fee = getCost(AccountTransactionType.ConfigureDelegation, state.payload);
const submit = async () => {
if (fee === undefined) {
throw Error('Fee could not be calculated');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default function DelegatorStake({ title, target, initialValues, existingV
const [highStakeWarning, setHighStakeWarning] = useState(false);

const values = form.watch();
const getCost = useGetTransactionFee(AccountTransactionType.ConfigureDelegation);
const getCost = useGetTransactionFee();
const fee = useMemo(() => {
let payload: ConfigureDelegationPayload;
try {
Expand All @@ -131,7 +131,7 @@ export default function DelegatorStake({ title, target, initialValues, existingV
existingValues
);
}
return getCost(payload);
return getCost(AccountTransactionType.ConfigureDelegation, payload);
}, [target, values, getCost]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type DelegationTypeForm = {
};

/** The form values for delegator stake configuration step */
export type DelegatorStakeForm = AmountForm & {
export type DelegatorStakeForm = Omit<AmountForm, 'token'> & {
/** Whether to add rewards to the stake or not */
redelegate: boolean;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function ValidationResult() {
};
const nav = useNavigate();
const { t } = useTranslation('x', { keyPrefix: 'earn.validator' });
const getCost = useGetTransactionFee(AccountTransactionType.ConfigureBaker);
const getCost = useGetTransactionFee();
const accountInfo = ensureDefined(useSelectedAccountInfo(), 'No account selected');
const [error, setError] = useState<Error>();

Expand Down Expand Up @@ -84,7 +84,7 @@ export default function ValidationResult() {
return null;
}

const fee = getCost(state.payload);
const fee = getCost(AccountTransactionType.ConfigureBaker, state.payload);
const submit = async () => {
if (fee === undefined) {
throw Error('Fee could not be calculated');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ export default function ValidatorStake({ title, initialValues, existingValues, o
const [highStakeWarning, setHighStakeWarning] = useState(false);

const values = form.watch();
const getCost = useGetTransactionFee(AccountTransactionType.ConfigureBaker);
const getCost = useGetTransactionFee();
const fee = useMemo(() => {
if (existingValues === undefined) {
return getCost(PAYLOAD_MAX);
return getCost(AccountTransactionType.ConfigureBaker, PAYLOAD_MAX);
}

try {
Expand All @@ -106,7 +106,7 @@ export default function ValidatorStake({ title, initialValues, existingValues, o
}

const payload: ConfigureBakerPayload = { stake, restakeEarnings: restake };
return getCost(payload);
return getCost(AccountTransactionType.ConfigureBaker, payload);
} catch {
// We failed to parse the amount
return undefined;
Expand All @@ -118,7 +118,7 @@ export default function ValidatorStake({ title, initialValues, existingValues, o
return undefined; // We know the cost as we don't depend on values set later in the flow
}

return getCost(PAYLOAD_MIN);
return getCost(AccountTransactionType.ConfigureBaker, PAYLOAD_MIN);
}, [getCost, existingValues]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OpenStatus,
OpenStatusText,
} from '@concordium/web-sdk';
import { AmountForm } from '@popup/popupX/shared/Form/TokenAmount';
import { formatCcdAmount, parseCcdAmount } from '@popup/popupX/shared/utils/helpers';
import i18n from '@popup/shell/i18n';

Expand Down Expand Up @@ -45,8 +46,7 @@ export function showCommissionRate(fraction: number): string {
export const isRange = (range: CommissionRange) => range.min !== range.max;

/** The form data for specifying validator stake */
export type ValidatorStakeForm = { amount: string; restake: boolean };

export type ValidatorStakeForm = Omit<AmountForm, 'token'> & { restake: boolean };
export type ValidatorFormUpdateStake = { stake: ValidatorStakeForm };
export type ValidatorFormUpdateKeys = { keys: GenerateBakerKeysOutput };
export type ValidatorFormUpdateSettings = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ReactNode, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { generatePath, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useAtomValue } from 'jotai';
import { displayAsCcd } from 'wallet-common-helpers';
Expand Down Expand Up @@ -118,7 +118,7 @@ function MainPageConfirmedAccount({ credential }: MainPageConfirmedAccountProps)
const { t } = useTranslation('x', { keyPrefix: 'mainPage' });

const nav = useNavigate();
const navToSend = () => nav(relativeRoutes.home.send.path);
const navToSend = () => nav(generatePath(absoluteRoutes.home.sendFunds.path, { account: credential.address }));
const navToReceive = () => nav(relativeRoutes.home.receive.path);
const navToTransactionLog = () =>
nav(relativeRoutes.home.transactionLog.path.replace(':account', credential.address));
Expand Down
145 changes: 145 additions & 0 deletions packages/browser-wallet/src/popup/popupX/pages/SendFunds/Confirm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
AccountAddress,
AccountTransactionType,
CIS2,
CIS2Contract,
CcdAmount,
Energy,
SimpleTransferPayload,
TransactionHash,
} from '@concordium/web-sdk';
import { useAsyncMemo } from 'wallet-common-helpers';
import { useAtomValue } from 'jotai';
import { useNavigate } from 'react-router-dom';

import Page from '@popup/popupX/shared/Page';
import Text from '@popup/popupX/shared/Text';
import Arrow from '@assets/svgX/arrow-right.svg';
import Card from '@popup/popupX/shared/Card';
import {
displayNameAndSplitAddress,
displaySplitAddress,
useSelectedCredential,
} from '@popup/shared/utils/account-helpers';
import { AmountReceiveForm } from '@popup/popupX/shared/Form/TokenAmount/View';
import { ensureDefined } from '@shared/utils/basic-helpers';
import { CCD_METADATA } from '@shared/constants/token-metadata';
import { formatCcdAmount, parseCcdAmount, parseTokenAmount } from '@popup/popupX/shared/utils/helpers';
import { useTransactionSubmit } from '@popup/shared/utils/transaction-helpers';
import Button from '@popup/popupX/shared/Button';
import { grpcClientAtom } from '@popup/store/settings';
import { logError } from '@shared/utils/log-helpers';
import { submittedTransactionRoute } from '@popup/popupX/constants/routes';

import { CIS2_TRANSFER_NRG_OFFSET, showToken, useTokenMetadata } from './util';
import { UpdateContractSubmittedLocationState } from '../SubmittedTransaction/SubmittedTransaction';

type Props = {
sender: AccountAddress.Type;
values: AmountReceiveForm;
fee: CcdAmount.Type;
};

export default function SendFundsConfirm({ values, fee, sender }: Props) {
const { t } = useTranslation('x', { keyPrefix: 'sendFunds' });
const credential = ensureDefined(useSelectedCredential(), 'Expected selected account to be available');
const tokenMetadata = useTokenMetadata(values.token, sender);
const nav = useNavigate();
const tokenName = useMemo(() => {
if (values.token.tokenType === 'ccd') return CCD_METADATA.name;
if (tokenMetadata === undefined || values.token.tokenType === undefined) return undefined;

return showToken(tokenMetadata, values.token.tokenAddress);
}, [tokenMetadata, values.token]);
const receiver = AccountAddress.fromBase58(values.receiver);
const submitTransaction = useTransactionSubmit(
sender,
values.token.tokenType === 'ccd' ? AccountTransactionType.Transfer : AccountTransactionType.Update
);
const grpcClient = useAtomValue(grpcClientAtom);
const contractClient = useAsyncMemo(
async () => {
if (values.token.tokenType !== 'cis2') {
return undefined;
}
return CIS2Contract.create(grpcClient, values.token.tokenAddress.contract);
},
logError,
[values.token, grpcClient]
);

const payload = useAsyncMemo(
async () => {
if (values.token.tokenType === 'cis2') {
if (contractClient === undefined) return undefined; // We wait for the client to be ready
if (tokenMetadata === undefined) throw new Error('No metadata for token');

const transfer: CIS2.Transfer = {
from: sender,
to: receiver,
tokenId: values.token.tokenAddress.id,
tokenAmount: parseTokenAmount(values.amount, tokenMetadata?.decimals),
};
const result = await contractClient.dryRun.transfer(sender, transfer);
return contractClient.createTransfer(
{ energy: Energy.create(result.usedEnergy.value + CIS2_TRANSFER_NRG_OFFSET) },
transfer
).payload;
}
if (values.token.tokenType === 'ccd') {
const p: SimpleTransferPayload = {
amount: parseCcdAmount(values.amount),
toAddress: receiver,
};
return p;
}

return undefined;
},
logError,
[values.token, sender, values.receiver, contractClient]
);

const submit = async () => {
if (payload === undefined || tokenName === undefined) {
throw Error('Payload could not be created...');
}

const tx = await submitTransaction(payload, fee);
const state: UpdateContractSubmittedLocationState = { type: 'cis2.transfer', amount: values.amount, tokenName };
nav(submittedTransactionRoute(TransactionHash.fromHexString(tx)), {
state,
});
};

return (
<Page className="send-funds-container">
<Page.Top heading={t('confirmation.title')} />

<Card className="send-funds-confirm__card" type="transparent">
<div className="send-funds-confirm__card_destination">
<Text.MainMedium>{displayNameAndSplitAddress(credential)}</Text.MainMedium>
<Arrow />
<Text.MainMedium>{displaySplitAddress(values.receiver)}</Text.MainMedium>
</div>
<Text.Capture>
{t('amount')} ({tokenName}
):
</Text.Capture>
<Text.HeadingLarge>{values.amount}</Text.HeadingLarge>
<Text.Capture>{t('estimatedFee', { fee: formatCcdAmount(fee) })}</Text.Capture>
</Card>

<Page.Footer>
<Button.Main
className="button-main"
onClick={submit}
label={t('sendFunds')}
disabled={payload === undefined}
/>
</Page.Footer>
</Page>
);
}

This file was deleted.

Loading
Loading