From c755c86c7a103d0c7632190455736f099ea70b6b Mon Sep 17 00:00:00 2001 From: Anastasios Date: Tue, 3 Sep 2024 14:50:57 +0400 Subject: [PATCH] refactor: remove brc20 send flow code --- package.json | 2 +- pnpm-lock.yaml | 12 +- .../common/hooks/use-stacks-explorer-link.ts | 2 +- .../validation/forms/amount-validators.ts | 11 -- src/app/components/status-pending.tsx | 12 -- src/app/components/status-ready.tsx | 12 -- .../brc20-token-asset-list.tsx | 27 +-- .../pending-brc-20-transfers.tsx | 133 ------------- src/app/pages/home/components/assets.tsx | 2 - .../send-inscription-review.tsx | 5 - .../form/brc20/brc20-choose-fee.tsx | 177 ------------------ .../brc20/brc20-send-form-confirmation.tsx | 149 --------------- .../form/brc20/brc20-send-form.tsx | 119 ------------ .../form/brc20/use-brc20-send-form.tsx | 87 --------- .../send-crypto-asset-form.routes.tsx | 11 -- .../send/sent-summary/brc20-sent-summary.tsx | 106 ----------- .../ordinals/brc20/brc20-tokens.hooks.ts | 51 ----- .../ordinals/brc20/use-check-order-status.ts | 71 ------- src/app/store/index.ts | 3 - src/app/store/ordinals/ordinals.slice.ts | 108 ----------- src/shared/route-urls.ts | 4 - 21 files changed, 10 insertions(+), 1094 deletions(-) delete mode 100644 src/app/components/status-pending.tsx delete mode 100644 src/app/components/status-ready.tsx delete mode 100644 src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx delete mode 100644 src/app/pages/send/sent-summary/brc20-sent-summary.tsx delete mode 100644 src/app/query/bitcoin/ordinals/brc20/use-check-order-status.ts delete mode 100644 src/app/store/ordinals/ordinals.slice.ts diff --git a/package.json b/package.json index 8a6a4a3305b..56949477c80 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "@leather.io/constants": "0.9.1", "@leather.io/crypto": "1.4.2", "@leather.io/models": "0.13.0", - "@leather.io/query": "2.7.0", + "@leather.io/query": "2.7.1", "@leather.io/stacks": "1.0.2", "@leather.io/tokens": "0.9.0", "@leather.io/ui": "1.16.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index babc9272d30..21402e67524 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,8 +47,8 @@ importers: specifier: 0.13.0 version: 0.13.0 '@leather.io/query': - specifier: 2.7.0 - version: 2.7.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1) + specifier: 2.7.1 + version: 2.7.1(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1) '@leather.io/stacks': specifier: 1.0.2 version: 1.0.2(encoding@0.1.13) @@ -2551,7 +2551,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.18.28': resolution: {integrity: sha512-fvbVPId6s6etindzP6Nzos/CS1NurMVy4JKozjebArHr63tBid5i/UY5Pp+4wTCAM20gB2SjRdwcwoL6HFC4Iw==} @@ -2779,8 +2779,8 @@ packages: '@leather.io/prettier-config@0.6.0': resolution: {integrity: sha512-QBKtLanfxFxXBlR58U/j8a6lBI0xzJzqqi36fXpGVp+9mJoEf6Ro6xrtFrixjW6seY6EOva4OApVnnPBsvOC/w==} - '@leather.io/query@2.7.0': - resolution: {integrity: sha512-IWPJ+nYH2OR7Eb2WZ9Dkvs98/ffdJ1aXyIEomExuOLkTEmsxQvdZe4Bm0MtBwqFdzzmM1C7PSmbPuR521rpkHA==} + '@leather.io/query@2.7.1': + resolution: {integrity: sha512-TIew8kDeYGUbFOBVcWeFZuYCaSa2Pp2xfDUPxf/BicIsyYqXxFGoB5fclDqugratAWw9aZTTqsYcbMzg/+pwew==} peerDependencies: react: '*' @@ -17635,7 +17635,7 @@ snapshots: - '@vue/compiler-sfc' - supports-color - '@leather.io/query@2.7.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1)': + '@leather.io/query@2.7.1(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1)': dependencies: '@fungible-systems/zone-file': 2.0.0 '@hirosystems/token-metadata-api-client': 1.2.0(encoding@0.1.13) diff --git a/src/app/common/hooks/use-stacks-explorer-link.ts b/src/app/common/hooks/use-stacks-explorer-link.ts index 059eeb6f001..df8f86ddfed 100644 --- a/src/app/common/hooks/use-stacks-explorer-link.ts +++ b/src/app/common/hooks/use-stacks-explorer-link.ts @@ -5,7 +5,7 @@ import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { openInNewTab } from '../utils/open-in-new-tab'; -export interface HandleOpenStacksTxLinkArgs { +interface HandleOpenStacksTxLinkArgs { searchParams?: URLSearchParams; txid: string; } diff --git a/src/app/common/validation/forms/amount-validators.ts b/src/app/common/validation/forms/amount-validators.ts index 65453e84b23..5f8221dbadd 100644 --- a/src/app/common/validation/forms/amount-validators.ts +++ b/src/app/common/validation/forms/amount-validators.ts @@ -131,14 +131,3 @@ export function stacksFungibleTokenAmountValidator(balance: Money) { }, }); } - -export function tokenAmountValidator(balance: Money) { - const { amount } = balance; - return amountValidator().test({ - message: formatInsufficientBalanceError(balance, sum => sum.amount.toString()), - test(value) { - if (!isNumber(value) || !amount) return false; - return new BigNumber(value).isLessThanOrEqualTo(amount); - }, - }); -} diff --git a/src/app/components/status-pending.tsx b/src/app/components/status-pending.tsx deleted file mode 100644 index be34c834425..00000000000 --- a/src/app/components/status-pending.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function StatusPending() { - return ( -
- ); -} diff --git a/src/app/components/status-ready.tsx b/src/app/components/status-ready.tsx deleted file mode 100644 index d0aabde4d7d..00000000000 --- a/src/app/components/status-ready.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function StatusReady() { - return ( -
- ); -} diff --git a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index 6a239cf408d..bec2a7ad5c8 100644 --- a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -1,13 +1,9 @@ -import { useNavigate } from 'react-router-dom'; - import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; import { Stack } from 'leather-styles/jsx'; import type { Brc20CryptoAssetInfo, CryptoAssetBalance, MarketData } from '@leather.io/models'; import { Brc20AvatarIcon } from '@leather.io/ui'; -import { RouteUrls } from '@shared/route-urls'; - import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; import type { AssetListVariant } from '@app/features/asset-list/asset-list'; @@ -32,24 +28,8 @@ function getBrc20TokenFiatBalance(token: Brc20TokenAssetDetails) { }); } -export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProps) { - const navigate = useNavigate(); - const { balance, isLoading } = useCurrentBtcCryptoAssetBalanceNativeSegwit(); - - const hasPositiveBtcBalanceForFees = - variant === 'interactive' && balance.availableBalance.amount.isGreaterThan(0); - - function navigateToBrc20SendForm(token: Brc20TokenAssetDetails) { - const { balance, holderAddress, info, marketData } = token; - navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', info.symbol), { - state: { - balance: balance.availableBalance, - holderAddress, - marketData, - ticker: info.symbol, - }, - }); - } +export function Brc20TokenAssetList({ tokens }: Brc20TokenAssetListProps) { + const { isLoading } = useCurrentBtcCryptoAssetBalanceNativeSegwit(); if (!tokens.length) return null; return ( @@ -61,9 +41,6 @@ export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProp icon={} isLoading={isLoading} key={token.info.symbol} - onSelectAsset={ - hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : undefined - } titleLeft={token.info.symbol} fiatBalance={getBrc20TokenFiatBalance(token)} /> diff --git a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx deleted file mode 100644 index 8e4c10f6831..00000000000 --- a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { Box, Flex, HStack, Stack } from 'leather-styles/jsx'; - -import { fetchInscripionById, useOrdinalsbotClient } from '@leather.io/query'; -import { BulletSeparator, Caption, Flag } from '@leather.io/ui'; -import { noop } from '@leather.io/utils'; - -import { RouteUrls } from '@shared/route-urls'; - -import { usePressable } from '@app/components/item-hover'; -import { StatusPending } from '@app/components/status-pending'; -import { StatusReady } from '@app/components/status-ready'; -import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; -import { useCheckOrderStatuses } from '@app/query/bitcoin/ordinals/brc20/use-check-order-status'; -import { - OrdinalsbotInscriptionStatus, - PendingBrc20Transfer, - usePendingBrc20Transfers, -} from '@app/store/ordinals/ordinals.slice'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; - -function StatusIcon({ status }: { status: OrdinalsbotInscriptionStatus }) { - switch (status) { - case 'pending': - return ; - case 'paid': - return ; - case 'waiting-for-indexer': - return ; - case 'ready': - return ; - default: - return null; - } -} -function StatusLabel({ status }: { status: OrdinalsbotInscriptionStatus }) { - switch (status) { - case 'pending': - return <>Paying for transfer inscription…; - case 'paid': - return ( - - <>Creating transfer inscription… - - ); - case 'waiting-for-indexer': - return ( - - <>Receiving transfer inscription… - - ); - case 'ready': - return <>Ready to transfer; - default: - return null; - } -} - -export function PendingBrc20TransferList() { - const transferOrders = usePendingBrc20Transfers(); - - useCheckOrderStatuses( - transferOrders.filter(order => order.status !== 'ready').map(order => order.id) - ); - - if (!transferOrders.length) return null; - - return ( - - - Pending BRC-20 transfers - - - {transferOrders.map(order => ( - - ))} - - - ); -} - -interface PendingBrcTransferProps { - order: PendingBrc20Transfer; -} -function PendingBrcTransfer({ order }: PendingBrcTransferProps) { - const [component, bind] = usePressable(order.status === 'ready'); - const navigate = useNavigate(); - const ordinalsbotClient = useOrdinalsbotClient(); - const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit(); - - const hasPositiveBtcBalanceForFees = balance.availableBalance.amount.isGreaterThan(0); - - return ( - { - // Really inefficient, find way to not have to refetch data - const { data: orderInfo } = await ordinalsbotClient.orderStatus(order.id); - const { data: inscription } = await fetchInscripionById( - (orderInfo as any).files[0].tx?.inscription - ); - navigate(RouteUrls.SendOrdinalInscription, { - state: { - inscription, - }, - }); - } - : noop - : noop - } - {...(order.status === 'ready' ? bind : {})} - > - <> - {order.amount} {order.tick} - - - - - }> - - - - - - {component} - - ); -} diff --git a/src/app/pages/home/components/assets.tsx b/src/app/pages/home/components/assets.tsx index 2568d5e08f0..fad7b948377 100644 --- a/src/app/pages/home/components/assets.tsx +++ b/src/app/pages/home/components/assets.tsx @@ -5,13 +5,11 @@ import { Stack } from 'leather-styles/jsx'; import { AssetList } from '@app/features/asset-list/asset-list'; import { Collectibles } from '@app/features/collectibles/collectibles'; -import { PendingBrc20TransferList } from '@app/features/pending-brc-20-transfers/pending-brc-20-transfers'; export function Assets() { return ( - diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx index 7a4bd5aaa36..858ea1c5f64 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx @@ -15,8 +15,6 @@ import { InfoCardRow, InfoCardSeparator } from '@app/components/info-card/info-c import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; import { Card } from '@app/components/layout'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { useAppDispatch } from '@app/store'; -import { inscriptionSent } from '@app/store/ordinals/ordinals.slice'; import { InscriptionPreviewCard } from '../../../components/inscription-preview-card/inscription-preview-card'; import { useSendInscriptionState } from './components/send-inscription-container'; @@ -33,7 +31,6 @@ function useSendInscriptionReviewState() { export function SendInscriptionReview() { const navigate = useNavigate(); - const dispatch = useAppDispatch(); const { arrivesIn, signedTx, recipient, feeRowValue } = useSendInscriptionReviewState(); const { inscription } = useSendInscriptionState(); @@ -47,8 +44,6 @@ export function SendInscriptionReview() { async onSuccess(txid: string) { void analytics.track('broadcast_ordinal_transaction'); await filteredUtxosQuery.refetch(); - // Might be a BRC-20 transfer, so we want to remove it from the pending - dispatch(inscriptionSent({ inscriptionId: inscription.id })); navigate(`/${RouteUrls.SendOrdinalInscription}/${RouteUrls.SendOrdinalInscriptionSent}`, { state: { inscription, diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx deleted file mode 100644 index fd4b702e5a6..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useState } from 'react'; -import { Outlet, useLocation, useNavigate } from 'react-router-dom'; - -import { Stack } from 'leather-styles/jsx'; -import get from 'lodash.get'; - -import type { BtcFeeType } from '@leather.io/models'; -import type { UtxoResponseItem } from '@leather.io/query'; -import { createMoney } from '@leather.io/utils'; - -import { logger } from '@shared/logger'; -import { RouteUrls } from '@shared/route-urls'; - -import { formFeeRowValue } from '@app/common/send/utils'; -import { useGenerateUnsignedNativeSegwitTx } from '@app/common/transactions/bitcoin/use-generate-bitcoin-tx'; -import { - BitcoinFeesList, - OnChooseFeeArgs, -} from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; -import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; -import { Content, Page } from '@app/components/layout'; -import { LoadingSpinner } from '@app/components/loading-spinner'; -import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; -import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; -import { PageHeader } from '@app/features/container/headers/page.header'; -import { useToast } from '@app/features/toasts/use-toast'; -import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; -import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; - -import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container'; - -function useBrc20ChooseFeeState() { - const location = useLocation(); - return { - ticker: get(location.state, 'ticker') as string, - amount: get(location.state, 'amount') as string, - recipient: get(location.state, 'recipient') as string, - utxos: get(location.state, 'utxos') as UtxoResponseItem[], - holderAddress: get(location.state, 'holderAddress') as string, - }; -} - -export function BrcChooseFee() { - const toast = useToast(); - const navigate = useNavigate(); - const { amount, recipient, ticker, utxos, holderAddress } = useBrc20ChooseFeeState(); - const generateTx = useGenerateUnsignedNativeSegwitTx(); - const signTx = useSignBitcoinTx(); - const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState(); - const { initiateTransfer } = useBrc20Transfers(holderAddress); - const amountAsMoney = createMoney(Number(amount), ticker, 0); - const { feesList, isLoading } = useBitcoinFeesList({ - amount: amountAsMoney, - recipient, - utxos, - }); - const recipients = [ - { - address: recipient, - amount: amountAsMoney, - }, - ]; - const recommendedFeeRate = feesList[1]?.feeRate.toString() || ''; - - const { showInsufficientBalanceError, onValidateBitcoinFeeSpend } = - useValidateBitcoinSpend(amountAsMoney); - - const [isLoadingOrder, setIsLoadingOrder] = useState(false); - - async function previewTransaction({ feeRate, feeValue, isCustomFee }: OnChooseFeeArgs) { - setIsLoadingOrder(true); - try { - const { order, id } = await initiateTransfer(ticker, amount); - setIsLoadingOrder(false); - - const { charge } = order.data; - - const serviceFeeRecipient = charge.address; - const serviceFee = charge.amount; - - const serviceFeeAsMoney = createMoney(serviceFee, 'BTC'); - - const resp = await generateTx( - { - amount: serviceFeeAsMoney, - recipients: [ - { - address: serviceFeeRecipient, - amount: serviceFeeAsMoney, - }, - ], - }, - feeRate, - utxos - ); - - if (!resp) return logger.error('Attempted to generate raw tx, but no tx exists'); - const signedTx = await signTx(resp.psbt); - - if (!signedTx) return logger.error('Attempted to sign tx, but no tx exists'); - signedTx.finalize(); - - const feeRowValue = formFeeRowValue(feeRate, isCustomFee); - navigate(RouteUrls.SendBrc20Confirmation.replace(':ticker', ticker), { - state: { - tx: signedTx.hex, - orderId: id, - fee: feeValue, - serviceFee: charge.amount, - serviceFeeRecipient, - recipient, - feeRowValue, - ticker, - amount, - holderAddress, - hasHeaderTitle: true, - }, - }); - } catch (err) { - const errorMessage = (err as Error).message || 'Failed to create transaction'; - logger.error('Failed to create transaction', err); - toast.error(errorMessage); - navigate('..'); - } finally { - setIsLoadingOrder(false); - } - } - - return ( - <> - - - - {isLoadingOrder && ( - - - - )} - {!isLoadingOrder && ( - <> - setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - selectedFeeType={selectedFeeType} - /> - } - isLoading={isLoading} - isSendingMax={false} - onChooseFee={previewTransaction} - onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - recommendedFeeRate={recommendedFeeRate} - recipients={recipients} - showError={showInsufficientBalanceError} - maxRecommendedFeeRate={feesList[0]?.feeRate} - /> - - - )} - - - - ); -} diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx deleted file mode 100644 index c4eeaa11c8d..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - -import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; -import { Stack } from 'leather-styles/jsx'; -import get from 'lodash.get'; - -import { decodeBitcoinTx } from '@leather.io/bitcoin'; -import { useBitcoinBroadcastTransaction } from '@leather.io/query'; -import { Button } from '@leather.io/ui'; -import { createMoney, formatMoney, formatMoneyPadded, sumMoney } from '@leather.io/utils'; - -import { RouteUrls } from '@shared/route-urls'; -import { analytics } from '@shared/utils/analytics'; - -import { - InfoCardAssetValue, - InfoCardRow, - InfoCardSeparator, -} from '@app/components/info-card/info-card'; -import { Card, Content, Page } from '@app/components/layout'; -import { PageHeader } from '@app/features/container/headers/page.header'; -import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; - -import { useSendFormNavigate } from '../../hooks/use-send-form-navigate'; - -function useBrc20SendFormConfirmationState() { - const location = useLocation(); - return { - orderId: get(location.state, 'orderId') as string, - fee: get(location.state, 'fee') as string, - feeRowValue: get(location.state, 'feeRowValue') as string, - serviceFee: get(location.state, 'serviceFee') as number, - serviceFeeRecipient: get(location.state, 'serviceFeeRecipient') as string, - recipient: get(location.state, 'recipient') as string, - ticker: get(location.state, 'ticker') as string, - amount: get(location.state, 'amount') as string, - tx: get(location.state, 'tx') as string, - holderAddress: get(location.state, 'holderAddress') as string, - }; -} - -export function Brc20SendFormConfirmation() { - const navigate = useNavigate(); - - const { amount, recipient, fee, ticker, serviceFee, tx, orderId, feeRowValue, holderAddress } = - useBrc20SendFormConfirmationState(); - - const summaryFeeMoney = createMoney(Number(fee), 'BTC'); - - const serviceFeeMoney = createMoney(serviceFee, 'BTC'); - const serviceFeeFormatted = formatMoneyPadded(serviceFeeMoney); - - const totalFee = sumMoney([summaryFeeMoney, serviceFeeMoney]); - const totalFeeFormatted = formatMoney(totalFee); - - const amountFormatted = formatMoney(createMoney(Number(amount), ticker, 0)); - const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction(); - const { filteredUtxosQuery } = useCurrentNativeSegwitUtxos(); - - const psbt = decodeBitcoinTx(tx); - const nav = useSendFormNavigate(); - - const { inscriptionPaymentTransactionComplete } = useBrc20Transfers(holderAddress); - - async function initiateTransaction() { - await broadcastTx({ - tx, - async onSuccess(txId: string) { - inscriptionPaymentTransactionComplete(orderId, Number(amount), recipient, ticker); - - void analytics.track('broadcast_transaction', { - symbol: ticker, - type: 'brc-20', - amount, - fee, - inputs: psbt.inputs.length, - outputs: psbt.inputs.length, - }); - await filteredUtxosQuery.refetch(); - navigate(RouteUrls.SentBrc20Summary.replace(':ticker', ticker), { - state: { - serviceFee: serviceFeeFormatted, - totalFee: totalFeeFormatted, - recipient, - ticker, - amount, - txId, - feeRowValue, - txLink: { - blockchain: 'bitcoin', - txid: txId || '', - }, - }, - }); - }, - onError(e) { - void analytics.track('broadcast_brc20_error', { - error: e, - }); - - nav.toErrorPage(e); - }, - }); - } - - return ( - <> - - - - - Create transfer inscription - - } - > - - - - - - - - - - - - - - - ); -} diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx deleted file mode 100644 index c1220fc368a..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { Outlet, useLocation } from 'react-router-dom'; - -import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; -import { Form, Formik } from 'formik'; -import { styled } from 'leather-styles/jsx'; -import get from 'lodash.get'; - -import type { MarketData, Money } from '@leather.io/models'; -import { Brc20AvatarIcon, Button, Callout, Link } from '@leather.io/ui'; -import { convertAmountToBaseUnit, formatMoney } from '@leather.io/utils'; - -import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { AvailableBalance, ButtonRow, Card, Content, Page } from '@app/components/layout'; -import { PageHeader } from '@app/features/container/headers/page.header'; - -import { AmountField } from '../../components/amount-field'; -import { SelectedAssetField } from '../../components/selected-asset-field'; -import { SendFiatValue } from '../../components/send-fiat-value'; -import { SendMaxButton } from '../../components/send-max-button'; -import { defaultSendFormFormikProps } from '../../send-form.utils'; -import { useBrc20SendForm } from './use-brc20-send-form'; - -function useBrc20SendFormRouteState() { - const { state } = useLocation(); - return { - balance: get(state, 'balance', '') as Money, - ticker: get(state, 'ticker', '') as string, - holderAddress: get(state, 'holderAddress', '') as string, - marketData: get(state, 'marketData') as MarketData, - }; -} - -export function Brc20SendForm() { - const { balance, ticker, holderAddress, marketData } = useBrc20SendFormRouteState(); - const { initialValues, chooseTransactionFee, validationSchema, formRef, onFormStateChange } = - useBrc20SendForm({ balance, ticker, holderAddress }); - - return ( - <> - - - - - {props => { - onFormStateChange(props.values); - return ( -
- - - - - } - > - - } - autoComplete="off" - switchableAmount={ - marketData ? ( - - ) : undefined - } - /> - } name={ticker} symbol={ticker} /> - - -
  • 1. Create transfer inscription with amount to send
  • -
  • 2. Send transfer inscription to recipient of choice
  • -
    - { - openInNewTab( - 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' - ); - }} - textStyle="body.02" - > - Learn more - -
    -
    - - - - ); - }} -
    -
    -
    - - ); -} diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx deleted file mode 100644 index 5d8eb3e23bd..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { FormikHelpers, FormikProps } from 'formik'; -import * as yup from 'yup'; - -import type { Money } from '@leather.io/models'; -import { noop } from '@leather.io/utils'; - -import { btcAddressNetworkValidator, btcAddressValidator } from '@shared/forms/address-validators'; -import { logger } from '@shared/logger'; -import { RouteUrls } from '@shared/route-urls'; - -import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { tokenAmountValidator } from '@app/common/validation/forms/amount-validators'; -import { currencyAmountValidator } from '@app/common/validation/forms/currency-validators'; -import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values'; -import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; - -import { createDefaultInitialFormValues } from '../../send-form.utils'; - -interface Brc20SendFormValues { - recipient: string; - amount: string; - symbol: string; -} - -interface UseBrc20SendFormArgs { - balance: Money; - ticker: string; - holderAddress: string; -} - -export function useBrc20SendForm({ balance, ticker, holderAddress }: UseBrc20SendFormArgs) { - const formRef = useRef>(null); - const { whenWallet } = useWalletType(); - const navigate = useNavigate(); - const currentNetwork = useCurrentNetwork(); - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - const { data: utxos = [], filteredUtxosQuery } = useCurrentNativeSegwitUtxos(); - - // Forcing a refetch to ensure UTXOs are fresh - useOnMount(() => filteredUtxosQuery.refetch()); - - // TODO: change recipient to that one user iputs - const initialValues = createDefaultInitialFormValues({ - recipient: nativeSegwitSigner.address, - amount: '', - symbol: ticker, - }); - - const validationSchema = yup.object({ - amount: yup.number().concat(currencyAmountValidator()).concat(tokenAmountValidator(balance)), - recipient: yup - .string() - .concat(btcAddressValidator()) - .concat(btcAddressNetworkValidator(currentNetwork.chain.bitcoin.bitcoinNetwork)), - }); - const { onFormStateChange } = useUpdatePersistedSendFormValues(); - - async function chooseTransactionFee( - values: Brc20SendFormValues, - formikHelpers: FormikHelpers - ) { - logger.debug('btc form values', values); - // Validate and check high fee warning first - await formikHelpers.validateForm(); - whenWallet({ - software: () => - navigate(RouteUrls.SendBrc20ChooseFee.replace(':ticker', ticker), { - state: { ...values, ticker, utxos, holderAddress, hasHeaderTitle: true }, - }), - ledger: noop, - })(); - } - - return { - initialValues, - chooseTransactionFee, - validationSchema, - formRef, - onFormStateChange, - }; -} diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 7b734acad88..21e92aac41c 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -14,14 +14,10 @@ import { AccountGate } from '@app/routes/account-gate'; import { BroadcastError } from '../broadcast-error/broadcast-error'; import { ChooseCryptoAsset } from '../choose-crypto-asset/choose-crypto-asset'; -import { Brc20SentSummary } from '../sent-summary/brc20-sent-summary'; import { BtcSentSummary } from '../sent-summary/btc-sent-summary'; import { StxSentSummary } from '../sent-summary/stx-sent-summary'; import { RecipientAccountsSheet } from './components/recipient-accounts-dialog/recipient-accounts-dialog'; import { SendBitcoinAssetContainer } from './family/bitcoin/components/send-bitcoin-asset-container'; -import { BrcChooseFee } from './form/brc20/brc20-choose-fee'; -import { Brc20SendForm } from './form/brc20/brc20-send-form'; -import { Brc20SendFormConfirmation } from './form/brc20/brc20-send-form-confirmation'; import { BtcChooseFee } from './form/btc/btc-choose-fee'; import { BtcSendForm } from './form/btc/btc-send-form'; import { BtcSendFormConfirmation } from './form/btc/btc-send-form-confirmation'; @@ -70,13 +66,6 @@ export const sendCryptoAssetFormRoutes = ( {ledgerBitcoinTxSigningRoutes} } /> - - } /> - }> - {ledgerBitcoinTxSigningRoutes} - - } /> - } /> - - - - - } - label="Pending BRC-20 transfers" - onClick={onClickLink} - /> - - } - > - - - - - - - - - You'll need to send the transfer inscription to your recipient of choice from - the home screen once its status changes to "Ready to send" - - { - openInNewTab( - 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' - ); - }} - > - Learn more - - - - - - - - - - - - - - - - - ); -} diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts index 6f3a7ca4043..69b4ed544a2 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts @@ -7,23 +7,16 @@ import { createMarketPair, } from '@leather.io/models'; import { - createBrc20TransferInscription, - encodeBrc20TransferInscription, isFetchedWithSuccess, - useAverageBitcoinFeeRates, useCalculateBitcoinFiatValue, useConfigOrdinalsbot, useGetBrc20TokensQuery, - useOrdinalsbotClient, } from '@leather.io/query'; import { createMoney, unitToFractionalUnit } from '@leather.io/utils'; -import { useAppDispatch } from '@app/store'; -import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import { brc20TransferInitiated } from '@app/store/ordinals/ordinals.slice'; // ts-unused-exports:disable-next-line export function useBrc20FeatureFlag() { @@ -46,50 +39,6 @@ export function useBrc20FeatureFlag() { return { enabled: true } as const; } -export function useBrc20Transfers(holderAddress: string) { - const dispatch = useAppDispatch(); - const currentAccountIndex = useCurrentAccountIndex(); - const ordinalsbotClient = useOrdinalsbotClient(); - const { data: fees } = useAverageBitcoinFeeRates(); - - return { - async initiateTransfer(tick: string, amount: string) { - const transferInscription = createBrc20TransferInscription(tick, Number(amount)); - const { payload, size } = encodeBrc20TransferInscription(transferInscription); - - const order = await ordinalsbotClient.order({ - receiveAddress: holderAddress, - file: payload, - size, - name: `${tick}-${amount}.txt`, - fee: fees?.halfHourFee.toNumber() ?? 10, - }); - - if (order.data.status !== 'ok') throw new Error('Failed to initiate transfer'); - - return { id: order.data.id, order }; - }, - - inscriptionPaymentTransactionComplete( - orderId: string, - amount: number, - recipient: string, - tick: string - ) { - dispatch( - brc20TransferInitiated({ - accountIndex: currentAccountIndex, - amount, - tick, - recipient, - status: 'pending', - id: orderId, - }) - ); - }, - }; -} - function createBrc20CryptoAssetInfo(decimals: number, ticker: string): Brc20CryptoAssetInfo { return { decimals, diff --git a/src/app/query/bitcoin/ordinals/brc20/use-check-order-status.ts b/src/app/query/bitcoin/ordinals/brc20/use-check-order-status.ts deleted file mode 100644 index 7adf7c5040f..00000000000 --- a/src/app/query/bitcoin/ordinals/brc20/use-check-order-status.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { useQueries } from '@tanstack/react-query'; - -import { fetchInscripionById, useOrdinalsbotClient } from '@leather.io/query'; - -import { useAppDispatch } from '@app/store'; -import { - brc20OrderNotFound, - brc20TransferAwaitingIndexer, - brc20TransferPaid, - brc20TransferReady, - usePendingBrc20TransferEntities, -} from '@app/store/ordinals/ordinals.slice'; - -export function useCheckOrderStatuses(ids: string[]) { - const ordinalsbotClient = useOrdinalsbotClient(); - - const transferMap = usePendingBrc20TransferEntities(); - const dispatch = useAppDispatch(); - - return useQueries({ - queries: ids.map(id => ({ - queryKey: ['check-order-status', id], - queryFn: async () => ordinalsbotClient.orderStatus(id), - async onSuccess({ data }: Awaited>) { - if (data.status === 'error') { - if (data.error.includes('no such order')) { - dispatch(brc20OrderNotFound({ id })); - } - return; - } - - const entry = transferMap[data.id]; - - if (!entry) return; - - const file = data.files[0]; - - // inscription reported by service - if ('tx' in file) { - // see if its on hiro indexer - try { - const { data: inscription } = await fetchInscripionById(file.tx?.inscription ?? ''); - // use number to determine legit - if (inscription.number) { - dispatch( - brc20TransferReady({ - id: data.id, - inscriptionId: file.tx?.inscription ?? '', - }) - ); - return; - } - } catch (error) {} - - // or say awaiting indexer - dispatch(brc20TransferAwaitingIndexer({ id: data.id })); - return; - } - - if (data.paid && entry.status !== 'paid') { - dispatch(brc20TransferPaid({ id: data.id })); - return; - } - }, - refetchInterval: 30000, - refetchOnMount: true, - refetchIntervalInBackground: true, - refetchOnWindowFocus: false, - })), - }); -} diff --git a/src/app/store/index.ts b/src/app/store/index.ts index 9a040547c6d..d0e8efe4ebe 100644 --- a/src/app/store/index.ts +++ b/src/app/store/index.ts @@ -30,7 +30,6 @@ import { inMemoryKeySlice } from './in-memory-key/in-memory-key.slice'; import { bitcoinKeysSlice } from './ledger/bitcoin/bitcoin-key.slice'; import { stacksKeysSlice } from './ledger/stacks/stacks-key.slice'; import { networksSlice } from './networks/networks.slice'; -import { ordinalsSlice } from './ordinals/ordinals.slice'; import { settingsSlice } from './settings/settings.slice'; import { keySlice } from './software-keys/software-key.slice'; import { submittedTransactionsSlice } from './submitted-transactions/submitted-transactions.slice'; @@ -45,7 +44,6 @@ export interface RootState { bitcoin: ReturnType; stacks: ReturnType; }; - ordinals: ReturnType; inMemoryKeys: ReturnType; softwareKeys: ReturnType; networks: ReturnType; @@ -62,7 +60,6 @@ const appReducer = combineReducers({ bitcoin: bitcoinKeysSlice.reducer, stacks: stacksKeysSlice.reducer, }), - ordinals: ordinalsSlice.reducer, inMemoryKeys: inMemoryKeySlice.reducer, softwareKeys: keySlice.reducer, networks: networksSlice.reducer, diff --git a/src/app/store/ordinals/ordinals.slice.ts b/src/app/store/ordinals/ordinals.slice.ts deleted file mode 100644 index d782413f8f9..00000000000 --- a/src/app/store/ordinals/ordinals.slice.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { useSelector } from 'react-redux'; - -import { PayloadAction, createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit'; - -import { RootState } from '..'; -import { selectCurrentAccountIndex } from '../software-keys/software-key.selectors'; - -export type OrdinalsbotInscriptionStatus = 'pending' | 'paid' | 'waiting-for-indexer' | 'ready'; - -interface BasePendingBrc20Transfer { - status: OrdinalsbotInscriptionStatus; - accountIndex: number; - id: string; - recipient: string; - amount: number; - tick: string; -} -interface ReadyPendingBrc20Transfer extends BasePendingBrc20Transfer { - status: 'ready'; - inscriptionId: string; - accountIndex: number; - id: string; - recipient: string; - amount: number; - tick: string; -} - -function isReadyPendingBrc20Transfer(t: PendingBrc20Transfer): t is ReadyPendingBrc20Transfer { - return t.status === 'ready'; -} - -export type PendingBrc20Transfer = BasePendingBrc20Transfer | ReadyPendingBrc20Transfer; - -const ordinalsAdapter = createEntityAdapter(); - -export const ordinalsSlice = createSlice({ - name: 'ordinals', - initialState: ordinalsAdapter.getInitialState(), - reducers: { - brc20TransferInitiated: ordinalsAdapter.addOne, - brc20TransferPaid(state, action: PayloadAction<{ id: string }>) { - const transfer = state.entities[action.payload.id]; - if (!transfer) return; - transfer.status = 'paid'; - }, - brc20TransferAwaitingIndexer(state, action: PayloadAction<{ id: string }>) { - const transfer = state.entities[action.payload.id]; - if (!transfer) return; - transfer.status = 'waiting-for-indexer'; - }, - brc20TransferReady(state, action: PayloadAction<{ id: string; inscriptionId: string }>) { - const transfer = state.entities[action.payload.id] as ReadyPendingBrc20Transfer | undefined; - if (!transfer) return; - transfer.status = 'ready'; - transfer.inscriptionId = action.payload.inscriptionId; - }, - brc20OrderNotFound(state, action: PayloadAction<{ id: string }>) { - const transferMatch = ordinalsAdapter - .getSelectors() - .selectAll(state) - .find(transfer => transfer.id === action.payload.id); - if (!transferMatch) return; - ordinalsAdapter.removeOne(state, transferMatch.id); - }, - inscriptionSent(state, action: PayloadAction<{ inscriptionId: string }>) { - const transferMatch = ordinalsAdapter - .getSelectors() - .selectAll(state) - .filter(isReadyPendingBrc20Transfer) - .find(pendingTransfer => pendingTransfer.inscriptionId === action.payload.inscriptionId); - if (!transferMatch) return; - ordinalsAdapter.removeOne(state, transferMatch.id); - }, - }, -}); - -export const { - brc20TransferInitiated, - brc20TransferPaid, - brc20TransferAwaitingIndexer, - brc20TransferReady, - brc20OrderNotFound, - inscriptionSent, -} = ordinalsSlice.actions; - -const selectors = ordinalsAdapter.getSelectors(); - -function selectBrc20TransferState(state: RootState) { - return state.ordinals; -} - -const selectAllTransfers = createSelector(selectBrc20TransferState, selectors.selectAll); - -const selectAllTransfersMap = createSelector(selectBrc20TransferState, selectors.selectEntities); - -const selectCurrentAccountBrc20Transfers = createSelector( - selectAllTransfers, - selectCurrentAccountIndex, - (transfers, accountIndex) => transfers.filter(t => t.accountIndex === accountIndex) -); - -export function usePendingBrc20Transfers() { - return useSelector(selectCurrentAccountBrc20Transfers); -} - -export function usePendingBrc20TransferEntities() { - return useSelector(selectAllTransfersMap); -} diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index fa45650284f..3e5230377b4 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -61,10 +61,6 @@ export enum RouteUrls { SendStacksSip10Confirmation = '/send/:symbol/confirm', SentBtcTxSummary = '/sent/btc/:txId', SentStxTxSummary = '/sent/stx/:txId', - SendBrc20SendForm = '/send/brc20/:ticker', - SendBrc20ChooseFee = '/send/brc20/:ticker/choose-fee', - SendBrc20Confirmation = '/send/brc20/:ticker/confirm', - SentBrc20Summary = '/send/brc20/:ticker/summary', // Send ordinal inscriptions SendOrdinalInscription = 'send/ordinal-inscription',