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

swap orders hooks fixing #3557

Merged
merged 12 commits into from
Jun 24, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import TextField from '../common/TextField';
import type { RemoteTokenInfo } from '../../api/ada/lib/state-fetch/types';
import LoadingSpinner from '../widgets/LoadingSpinner';
import { useState } from 'react';
import type { FormattedTokenValue } from '../../containers/swap/orders/OrdersPage';
import { WrongPassphraseError } from '../../api/ada/lib/cardanoCrypto/cryptoErrors';
import { stringifyError } from '../../utils/logging';
import { InfoTooltip } from '../widgets/InfoTooltip';
Expand All @@ -16,6 +15,7 @@ import type { TokenLookupKey } from '../../api/common/lib/MultiToken';
import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables';
import { SelectedExplorer } from '../../domain/SelectedExplorer';
import type LocalizableError from '../../i18n/LocalizableError';
import type { FormattedTokenValue } from '../../containers/swap/orders/util';

type Props = {|
order: any,
Expand Down Expand Up @@ -102,9 +102,9 @@ export default function CancelSwapOrderDialog({
>
{transactionParams ? (
transactionParams.returnValues.map((v, index) => (
<>
<Box key={v.ticker}>
{index > 0 && ' +'} {v.formattedValue} {v.ticker}
</>
</Box>
))
) : (
<LoadingSpinner small />
Expand Down
72 changes: 0 additions & 72 deletions packages/yoroi-extension/app/containers/swap/hooks.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//@flow
import {
useSwap,
useSwapOrdersByStatusCompleted,
useSwapOrdersByStatusOpen,
useSwapTokensOnlyVerified,
} from '@yoroi/swap';
import { Quantities } from '../../utils/quantities';
import { useSwapForm } from './context/swap-form';
Expand Down Expand Up @@ -72,72 +69,3 @@ export function useSwapFeeDisplay(
formattedFee,
};
}

export function useRichOpenOrders(): any {
let openOrders = [];
try {
openOrders = useSwapOrdersByStatusOpen();
} catch (e) {
console.warn('useRichCompletedOrders.useSwapOrdersByStatusOpen', e);
}
let onlyVerifiedTokens = [];
try {
const res = useSwapTokensOnlyVerified();
onlyVerifiedTokens = res.onlyVerifiedTokens;
} catch (e) {
console.warn('useRichCompletedOrders.useSwapTokensOnlyVerified', e);
}
if ((openOrders?.length || 0) === 0 || (onlyVerifiedTokens?.length || 0) === 0) return [];
try {
const tokensMap = onlyVerifiedTokens.reduce((map, t) => ({ ...map, [t.id]: t }), {});
return openOrders.map(o => {
const fromToken = tokensMap[o.from.tokenId];
const toToken = tokensMap[o.to.tokenId];
return {
utxo: o.utxo,
from: { quantity: o.from.quantity, token: fromToken },
to: { quantity: o.to.quantity, token: toToken },
batcherFee: o.batcherFee,
valueAttached: o.valueAttached,
deposit: o.deposit,
provider: o.provider,
sender: o.sender,
};
});
} catch (e) {
console.warn('useRichOpenOrders', e);
return [];
}
}

export function useRichCompletedOrders(): any {
let completedOrders = [];
try {
completedOrders = useSwapOrdersByStatusCompleted();
} catch (e) {
console.warn('useRichCompletedOrders.useSwapOrdersByStatusCompleted', e);
}
let onlyVerifiedTokens = [];
try {
const res = useSwapTokensOnlyVerified();
onlyVerifiedTokens = res.onlyVerifiedTokens;
} catch (e) {
console.warn('useRichCompletedOrders.useSwapTokensOnlyVerified', e);
}
if ((completedOrders?.length || 0) === 0 || (onlyVerifiedTokens?.length || 0) === 0) return [];
try {
const tokensMap = onlyVerifiedTokens.reduce((map, t) => ({ ...map, [t.id]: t }), {});
return completedOrders.map(o => {
const fromToken = tokensMap[o.from.tokenId];
const toToken = tokensMap[o.to.tokenId];
return {
txHash: o.txHash,
from: { quantity: o.from.quantity, token: fromToken },
to: { quantity: o.to.quantity, token: toToken },
};
});
} catch (e) {
console.warn('useRichCompletedOrders', e);
return [];
}
}
155 changes: 22 additions & 133 deletions packages/yoroi-extension/app/containers/swap/orders/OrdersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@ import Table from '../../../components/common/table/Table';
import CancelSwapOrderDialog from '../../../components/swap/CancelOrderDialog';
import AssetPair from '../../../components/common/assets/AssetPair';
import Tabs from '../../../components/common/tabs/Tabs';
import { useRichCompletedOrders, useRichOpenOrders } from '../hooks';
import type { MappedOrder } from './hooks';
import { useRichOrders } from './hooks';
import type { StoresAndActionsProps } from '../../../types/injectedProps.types';
import { SwapPoolLabel } from '../../../components/swap/SwapPoolComponents';
import ExplorableHashContainer from '../../widgets/ExplorableHashContainer';
import { truncateAddressShort } from '../../../utils/formatters';
import { Quantities } from '../../../utils/quantities';
import { PRICE_PRECISION } from '../../../components/swap/common';
import { fail, forceNonNull, maybe, noop } from '../../../coreUtils';
import { fail, forceNonNull, maybe } from '../../../coreUtils';
import type { RemoteTokenInfo } from '../../../api/ada/lib/state-fetch/types';
import { useSwap } from '@yoroi/swap';
import { addressBech32ToHex } from '../../../api/ada/lib/cardanoCrypto/utils';
import {
getTransactionFeeFromCbor,
getTransactionTotalOutputFromCbor,
} from '../../../api/ada/transactions/utils';
import { getTransactionFeeFromCbor, getTransactionTotalOutputFromCbor, } from '../../../api/ada/transactions/utils';
import { SelectedExplorer } from '../../../domain/SelectedExplorer';
import type { CardanoConnectorSignRequest } from '../../../connector/types';
import { genLookupOrFail } from '../../../stores/stateless/tokenHelpers';
import moment from 'moment';
import { signTransactionHex } from '../../../api/ada/transactions/signTransactionHex';
import { createFormattedTokenValues } from './util';
import type { FormattedTokenValue } from './util';

type ColumnContext = {|
completedOrders: boolean,
Expand Down Expand Up @@ -83,119 +82,9 @@ const orderColumns: Array<Column> = [
},
];

export type FormattedTokenValue = {|
value: string,
formattedValue: string,
ticker: string,
|};

function createFormattedTokenValues({
entries,
order,
defaultTokenInfo,
}: {|
entries: Array<{| id: string, amount: string |}>,
order: any,
defaultTokenInfo: RemoteTokenInfo,
|}): Array<FormattedTokenValue> {
const tokenAmountMap = entries.reduce(
(map, v) => ({ ...map, [v.id]: Quantities.sum([map[v.id] ?? '0', v.amount]) }),
{}
);
const ptDecimals = forceNonNull(defaultTokenInfo.decimals);
// $FlowIgnore[prop-missing]
const defaultTokenValue = tokenAmountMap[''] ?? tokenAmountMap['.'] ?? '0';
const formattedTokenValues = [
{
value: defaultTokenValue,
formattedValue: Quantities.format(defaultTokenValue, ptDecimals, ptDecimals),
ticker: defaultTokenInfo.ticker ?? '-',
},
];
[order.from.token, order.to.token].forEach(t => {
if (t.id !== '' && t.id !== '.') {
maybe(tokenAmountMap[t.id], v => {
const formattedValue = Quantities.format(v, t.decimals, t.decimals);
formattedTokenValues.push({
value: v,
formattedValue,
ticker: t.ticker ?? '-',
});
});
}
});
return formattedTokenValues;
}

function mapOrderAssets(
order: any,
defaultTokenInfo: RemoteTokenInfo
): {|
price: string,
amount: string,
totalValues: ?Array<FormattedTokenValue>,
from: any,
to: any,
|} {
const price = Quantities.quotient(order.from.quantity, order.to.quantity);
const fromDecimals = order.from.token?.decimals ?? 0;
const toDecimals = order.to.token?.decimals ?? 0;
const priceDenomination = fromDecimals - toDecimals;
const formattedPrice = Quantities.format(price, priceDenomination, PRICE_PRECISION);
const formattedToQuantity = Quantities.format(
order.to.quantity,
toDecimals,
toDecimals
);
const formattedAttachedValues = maybe(order.valueAttached, val =>
createFormattedTokenValues({
entries: val.map(({ token: id, amount }) => ({ id, amount })),
order,
defaultTokenInfo,
})
);
return {
price: formattedPrice,
amount: formattedToQuantity,
totalValues: formattedAttachedValues,
from: order.from,
to: order.to,
};
}

type MappedOrder = {|
txId: string,
utxo?: string,
sender?: string,
provider?: string,
price: string,
amount: string,
totalValues: ?Array<FormattedTokenValue>,
from: any,
to: any,
|};

function mapOpenOrder(order: any, defaultTokenInfo: RemoteTokenInfo): MappedOrder {
const txId = order.utxo.split('#')[0];
return {
txId,
utxo: order.utxo,
sender: order.sender,
provider: order.provider,
...mapOrderAssets(order, defaultTokenInfo),
};
}

function mapCompletedOrder(order: any, defaultTokenInfo: RemoteTokenInfo): MappedOrder {
return {
txId: order.txHash,
...mapOrderAssets(order, defaultTokenInfo),
};
}

export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
const {
order: { cancel: swapCancelOrder },
order: orderApi,
} = useSwap();

const [showCompletedOrders, setShowCompletedOrders] = useState<boolean>(false);
Expand All @@ -207,32 +96,31 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
isSubmitting?: boolean,
|}>(null);

const wallet = props.stores.wallets.selectedOrFail;
const { wallets, tokenInfoStore, explorers, substores: { ada: { swapStore } } } = props.stores;

const wallet = wallets.selectedOrFail;
const network = wallet.getParent().getNetworkInfo();
const walletVariant = wallet.getParent().getWalletVariant();
const defaultTokenInfo = props.stores.tokenInfoStore.getDefaultTokenInfoSummary(
const defaultTokenInfo = tokenInfoStore.getDefaultTokenInfoSummary(
network.NetworkId
);

const selectedExplorer =
props.stores.explorers.selectedExplorer.get(network.NetworkId) ??
explorers.selectedExplorer.get(network.NetworkId) ??
fail('No explorer for wallet network');

const openOrders = useRichOpenOrders().map(o => mapOpenOrder(o, defaultTokenInfo));
const completedOrders = useRichCompletedOrders().map(o => mapCompletedOrder(o, defaultTokenInfo));

const txHashes = [...openOrders, ...completedOrders].map(o => o.txId);
noop(props.stores.substores.ada.swapStore.fetchTransactionTimestamps({ wallet, txHashes }));
const fetchTransactionTimestamps = txHashes => swapStore.fetchTransactionTimestamps({ wallet, txHashes });
let { openOrders, completedOrders, transactionTimestamps } = useRichOrders(defaultTokenInfo, fetchTransactionTimestamps);

const txHashToRenderedTimestamp: string => string = txHash => {
const date = props.stores.substores.ada.swapStore.transactionTimestamps[txHash];
const date = transactionTimestamps[txHash];
return date == null ? '-' : moment(date).format('MMM D, YYYY H:mm');
};

const handleCancelRequest = async order => {
setCancellationState({ order, tx: null });
try {
let utxoHex = await props.stores.substores.ada.swapStore.getCollateralUtxoHexForCancel({
let utxoHex = await swapStore.getCollateralUtxoHexForCancel({
wallet,
});
let collateralReorgTxHex: ?string = null;
Expand All @@ -242,7 +130,7 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
unsignedTxHex,
txData,
collateralUtxoHex,
} = await props.stores.substores.ada.swapStore.createCollateralReorgForCancel({ wallet });
} = await swapStore.createCollateralReorgForCancel({ wallet });
collateralReorgTxHex = unsignedTxHex;
collateralReorgTxData = txData;
utxoHex = collateralUtxoHex;
Expand Down Expand Up @@ -270,7 +158,7 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
throw new Error('Cannot cancel a completed order (sender == null)');
}
try {
const cancelTxCbor = await swapCancelOrder({
const cancelTxCbor = await orderApi.cancel({
address: addressBech32ToHex(sender),
utxos: {
order: order.utxo,
Expand All @@ -286,7 +174,8 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
id: e.identifier,
amount: e.amount.toString(),
})),
order,
from: order.from,
to: order.to,
defaultTokenInfo,
});
const formattedFeeValue = Quantities.format(
Expand Down Expand Up @@ -358,7 +247,7 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
signedCollateralReorgTx != null
? [signedCollateralReorgTx, signedCancelTx]
: [signedCancelTx];
await props.stores.substores.ada.swapStore.executeTransactionHexes({
await swapStore.executeTransactionHexes({
wallet,
signedTransactionHexes,
});
Expand Down Expand Up @@ -440,7 +329,7 @@ export default function SwapOrdersPage(props: StoresAndActionsProps): Node {
onCancelOrder={handleCancelConfirm}
onDialogClose={() => setCancellationState(null)}
defaultTokenInfo={defaultTokenInfo}
getTokenInfo={genLookupOrFail(props.stores.tokenInfoStore.tokenInfo)}
getTokenInfo={genLookupOrFail(tokenInfoStore.tokenInfo)}
selectedExplorer={selectedExplorer}
submissionError={null}
walletType={walletVariant}
Expand Down
Loading
Loading