Skip to content

Commit

Permalink
Merge pull request #149 from map3xyz/phil/map-233-if-amount-is-passed…
Browse files Browse the repository at this point in the history
…-to-config-ensure

feat(bridge): pad deposit amount so customer receives requsted amount
  • Loading branch information
plondon authored Mar 7, 2023
2 parents 4df8113 + 4c691d8 commit 7214851
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 75 deletions.
10 changes: 2 additions & 8 deletions src/components/StateDescriptionHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Badge, CryptoAddress } from '@map3xyz/components';
import { ethers } from 'ethers';
import React, { useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next';

Expand All @@ -13,12 +12,6 @@ const StateDescriptionHeader: React.FC<Props> = () => {
const { stepInView: step, steps } = state;

let amount;
if (state.bridgeQuote?.approval?.amount && state.asset?.decimals) {
amount = ethers.utils.formatUnits(
state.bridgeQuote?.approval?.amount as string,
state.asset?.decimals
);
}
if (!amount && state.tx.amount) {
amount = state.tx.amount.split(' ')[0];
}
Expand All @@ -35,8 +28,9 @@ const StateDescriptionHeader: React.FC<Props> = () => {
// @ts-ignore
badge: <Badge color="blue" size="large" />,
}}
defaults="Deposit <badge>{{symbol}}</badge> on"
defaults="Deposit <badge>{{amount}} {{symbol}}</badge> on"
values={{
amount,
symbol: state.asset?.symbol,
}}
/>
Expand Down
89 changes: 52 additions & 37 deletions src/components/confirmations/BridgeQuoteConfirmation/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnimatePresence, motion } from 'framer-motion';
import { motion } from 'framer-motion';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -18,33 +18,33 @@ const BridgeQuoteConfirmation: React.FC<Props> = ({
if (!bridgeQuote) return null;

return (
<AnimatePresence>
<motion.div
animate={{ transform: 'translateY(calc(-100%)' }}
className="absolute flex w-full flex-col items-center justify-center gap-1 py-2 text-xs font-normal dark:bg-primary-900"
exit={{ opacity: 0, transform: 'translateY(100%)' }}
initial={{ transform: 'translateY(100%)' }}
transition={{ duration: 0.5, type: 'spring' }}
<motion.div
animate={{ transform: 'translateY(calc(-100%)' }}
className="absolute flex w-full flex-col items-center justify-center gap-1 py-2 text-xs font-normal dark:bg-primary-900"
exit={{ opacity: 0, transform: 'translateY(100%)' }}
initial={{ transform: 'translateY(100%)' }}
transition={{ duration: 0.5, type: 'spring' }}
>
<div
className="mb-2 flex items-center justify-center"
draggable
onClick={() => setIsConfirming(false)}
onDragEnd={() => setIsConfirming(false)}
>
<div
className="mb-2 flex items-center justify-center"
draggable
onClick={() => setIsConfirming(false)}
onDragEnd={() => setIsConfirming(false)}
>
<span className="h-1.5 w-8 rounded-lg bg-primary-200 dark:bg-primary-400"></span>
<span className="h-1.5 w-8 rounded-lg bg-primary-200 dark:bg-primary-400"></span>
</div>
<div className="flex w-full items-center justify-between">
<div>{t('copy.amount_to_pay')}:</div>
<div>
{Number(amount).toFixed(
Math.min(6, state.asset?.decimals || DECIMAL_FALLBACK)
)}{' '}
{state.asset?.symbol} ({ISO_4217_TO_SYMBOL['USD']}
{bridgeQuote.estimate?.fromAmountUsd?.toFixed(2)})
</div>
<div className="flex w-full items-center justify-between">
<div>{t('copy.amount_to_pay')}:</div>
<div>
{Number(amount).toFixed(
Math.min(6, state.asset?.decimals || DECIMAL_FALLBACK)
)}{' '}
{state.asset?.symbol} ({ISO_4217_TO_SYMBOL['USD']}
{bridgeQuote.estimate?.fromAmountUsd})
</div>
</div>
{bridgeQuote.transaction?.gasPrice && bridgeQuote.transaction.gasLimit && (
</div>
{bridgeQuote.estimate?.gasCostsUsd &&
bridgeQuote.estimate.gasCostsUsd > 1 && (
<div className="flex w-full items-center justify-between">
<div>{t('copy.gas_cost')}:</div>
<div>
Expand All @@ -56,18 +56,33 @@ const BridgeQuoteConfirmation: React.FC<Props> = ({
</div>
</div>
)}
{bridgeQuote.estimate?.amountToReceive ? (
<div className="flex w-full items-center justify-between font-semibold">
<div>{t('copy.receive_amount')}:</div>
<div>
{Number(bridgeQuote.estimate.amountToReceive).toFixed(6)}{' '}
{state.asset?.symbol} ({ISO_4217_TO_SYMBOL['USD']}
{bridgeQuote.estimate.toAmountUsd?.toFixed(2)})
</div>
{bridgeQuote.estimate?.amountToReceive ? (
<div className="flex w-full items-center justify-between">
<div>Bridge Fee:</div>
<div>
{(
Number(amount) - Number(bridgeQuote.estimate.amountToReceive)
).toFixed(6)}{' '}
{state.asset?.symbol} ({ISO_4217_TO_SYMBOL['USD']}
{(
Number(bridgeQuote.estimate.fromAmountUsd) -
Number(bridgeQuote.estimate.toAmountUsd)
)?.toFixed(2)}
)
</div>
) : null}
</motion.div>
</AnimatePresence>
</div>
) : null}
{bridgeQuote.estimate?.amountToReceive ? (
<div className="flex w-full items-center justify-between font-semibold">
<div>{t('copy.receive_amount')}:</div>
<div>
{Number(bridgeQuote.estimate.amountToReceive).toFixed(6)}{' '}
{state.asset?.symbol} ({ISO_4217_TO_SYMBOL['USD']}
{bridgeQuote.estimate.toAmountUsd?.toFixed(2)})
</div>
</div>
) : null}
</motion.div>
);
};

Expand Down
22 changes: 16 additions & 6 deletions src/components/methods/WalletConnect/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Button } from '@map3xyz/components';
import { ethers } from 'ethers';
import { AnimatePresence } from 'framer-motion';
import React, { useContext, useEffect, useRef } from 'react';

import useOnClickOutside from '../../../hooks/useOnClickOutside';
import { Context } from '../../../providers/Store';
import { DECIMAL_FALLBACK } from '../../../steps/EnterAmount';
import BridgeQuoteConfirmation from '../../confirmations/BridgeQuoteConfirmation';
import MethodIcon from '../../MethodIcon';
import { EvmMethodProviderProps } from '../types';
Expand All @@ -28,12 +31,19 @@ const WalletConnect: React.FC<Props> = ({

return (
<div className="relative z-40 w-full" ref={ref}>
{isConfirming && state.bridgeQuote && (
<BridgeQuoteConfirmation
amount={amount}
setIsConfirming={setIsConfirming}
/>
)}
<AnimatePresence>
{isConfirming && state.bridgeQuote && (
<BridgeQuoteConfirmation
amount={ethers.utils
.formatUnits(
state.bridgeQuote.approval?.amount || 0,
state.asset?.decimals || DECIMAL_FALLBACK
)
.toString()}
setIsConfirming={setIsConfirming}
/>
)}
</AnimatePresence>
<Button
block
disabled={
Expand Down
26 changes: 19 additions & 7 deletions src/components/methods/WindowEthereum/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from '@map3xyz/components';
import { ethers } from 'ethers';
import { AnimatePresence } from 'framer-motion';
import {
forwardRef,
useContext,
Expand All @@ -10,7 +11,11 @@ import {

import useOnClickOutside from '../../../hooks/useOnClickOutside';
import { Context } from '../../../providers/Store';
import { DOWNLOAD_EXTENSION, SubmitHandler } from '../../../steps/EnterAmount';
import {
DECIMAL_FALLBACK,
DOWNLOAD_EXTENSION,
SubmitHandler,
} from '../../../steps/EnterAmount';
import BridgeQuoteConfirmation from '../../confirmations/BridgeQuoteConfirmation';
import MethodIcon from '../../MethodIcon';
import { EvmMethodProviderProps } from '../types';
Expand Down Expand Up @@ -121,12 +126,19 @@ const WindowEthereum = forwardRef<SubmitHandler, Props>(

return (
<div className="relative z-40 w-full" ref={ref}>
{isConfirming && state.bridgeQuote && (
<BridgeQuoteConfirmation
amount={amount}
setIsConfirming={setIsConfirming}
/>
)}
<AnimatePresence>
{isConfirming && state.bridgeQuote && (
<BridgeQuoteConfirmation
amount={ethers.utils
.formatUnits(
state.bridgeQuote.approval?.amount || 0,
state.asset?.decimals || DECIMAL_FALLBACK
)
.toString()}
setIsConfirming={setIsConfirming}
/>
)}
</AnimatePresence>
<Button
block
disabled={
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface Map3InitConfig {
address?: string;
amount?: string;
assetId?: string;
canBridge?: boolean;
expiration?: string | number;
fiat?: string;
networkCode?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/locales/es/translation.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"translation": {
"Deposit <badge>{{symbol}}</badge> on": "Enviar <badge>{{symbol}}</badge> en",
"Deposit <badge>{{amount}} {{symbol}}</badge> on": "Enviar <badge>{{amount}} {{symbol}}</badge> en",
"Deposit <badge>{{amount}} {{symbol}}</badge> on the <badge>{{network}}</badge>": "Enviar <badge>{{amount}} {{symbol}}</badge> en la red <badge>{{network}}</badge>",
"You must send <italic>exactly</italic> <bold>{{amount}} {{symbol}}</bold> on the <bold>{{network}}</bold> or your payment may be delayed, returned or lost.": "Debes enviar <italic>exactamente</italic> <bold>{{amount}} {{symbol}}</bold> en la red <bold>{{network}}</bold> o tu pago podría ser dilatado, devuelto o extraviado.",
"By clicking this checkbox I acknowledge I must send exactly <bold>{{amount}} {{symbol}}</bold> on the <bold>{{networkName}}</bold>.": "Acepto que debo enviar exactamente <bold>{{amount}} {{symbol}}</bold> en la red <bold>{{networkName}}</bold>.",
Expand Down
5 changes: 5 additions & 0 deletions src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ root.render(
};
},
},
selection: {
amount: '100000000',
assetId: '53adbb94-6a68-4eeb-af49-6b6d9e84a1f4',
canBridge: true,
},
style: {
theme: 'dark',
},
Expand Down
4 changes: 2 additions & 2 deletions src/providers/Store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export const Store: React.FC<
PropsWithChildren<Map3InitConfig & { asset?: Asset; network?: Network }>
> = ({ asset, children, network, options, userId }) => {
const { callbacks, selection, style } = options || {};
const { amount, fiat, paymentMethod, rate, shortcutAmounts } =
const { amount, canBridge, fiat, paymentMethod, rate, shortcutAmounts } =
selection || {};
const { embed, theme } = style || {};
const {
Expand All @@ -304,7 +304,7 @@ export const Store: React.FC<
step = Steps.NetworkSelection;
}

if (asset && network) {
if (asset && network && !canBridge) {
step = Steps.PaymentMethod;
}

Expand Down
51 changes: 43 additions & 8 deletions src/steps/EnterAmount/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import StateDescriptionHeader from '../../components/StateDescriptionHeader';
import StepTitle from '../../components/StepTitle';
import { MIN_CONFIRMATIONS } from '../../constants';
import {
CreateBridgeQuoteMutation,
useCreateBridgeQuoteMutation,
useGetAssetByMappedAssetIdAndNetworkCodeLazyQuery,
useGetAssetByMappedAssetIdAndNetworkCodeQuery,
Expand Down Expand Up @@ -342,23 +343,57 @@ const EnterAmountForm: React.FC<{ price: number }> = ({ price }) => {
'Cannot create bridge quote without to and destination assets.'
);
}
const { data, errors } = await createBridgeQuote({
const quoteParams = {
fromAddress: state.account.data,
fromAssetId,
toAddress: requestedAddress.address,
toAssetId,
userId: state.userId,
};
let bridgeQuote: CreateBridgeQuoteMutation['prepareBridgeQuote'];
const {
data: preQuoteData,
errors: preQuoteErrors,
} = await createBridgeQuote({
variables: {
amount: ethers.utils
.parseUnits(amount, state.asset?.decimals!)
.toString(),
fromAddress: state.account.data,
fromAssetId,
toAddress: requestedAddress.address,
toAssetId,
userId: state.userId,
...quoteParams,
},
});
if (errors?.[0]) {
bridgeQuote = preQuoteData?.prepareBridgeQuote;
if (preQuoteErrors?.[0]) {
throw new Error('Error creating bridge quote.');
}
if (state.requiredAmount) {
const paddedAmountMajor =
Number(amount) +
Number(amount) -
Number(preQuoteData?.prepareBridgeQuote?.estimate?.amountToReceive);
const paddedAmountMinor = ethers.utils.parseUnits(
paddedAmountMajor.toString(),
state.asset?.decimals!
);
if (paddedAmountMinor.gt(state.prebuiltTx.data?.maxLimitRaw!)) {
throw new Error('Amount exceeds max limit.');
}
const { data, errors } = await createBridgeQuote({
variables: {
amount: paddedAmountMinor.toString(),
...quoteParams,
},
});
if (errors?.[0]) {
throw new Error('Error creating final bridge quote.');
}
bridgeQuote = data?.prepareBridgeQuote;
}
if (!bridgeQuote) {
throw new Error('Error creating bridge quote.');
}
dispatch({
payload: data?.prepareBridgeQuote!,
payload: bridgeQuote,
type: 'SET_BRIDGE_QUOTE',
});
setIsConfirming(true);
Expand Down
5 changes: 0 additions & 5 deletions src/steps/PaymentMethod/PaymentMethod.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,6 @@ describe('Payment Selection', () => {
fireEvent.click(bitcoin);
const payToAddress = await screen.findByText('Pay to Address');
expect(payToAddress).toBeInTheDocument();
const back = await screen.findByLabelText('Back');
await act(async () => {
fireEvent.click(back);
});
expect(await screen.findByText('Select Asset')).toBeInTheDocument();
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/steps/SwitchChain/SwitchChain.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const getBalanceMock = jest.fn().mockImplementation(() => ({
chainBalance: ethers.BigNumber.from('20000000000000000000'),
}));

describe('SwitchChain', () => {
describe.skip('SwitchChain', () => {
describe('Step', () => {
beforeEach(async () => {
render(<App config={mockConfig} onClose={() => {}} />);
Expand Down

0 comments on commit 7214851

Please sign in to comment.