Skip to content

Commit

Permalink
feat: add inscription send flow testing, closes leather-io/issues#191
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo authored and pete-watters committed Sep 9, 2024
1 parent 3450e0a commit 54fce3e
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
"@leather.io/constants": "0.9.2",
"@leather.io/crypto": "1.4.2",
"@leather.io/models": "0.13.0",
"@leather.io/query": "2.8.0",
"@leather.io/query": "2.10.0",
"@leather.io/stacks": "1.0.2",
"@leather.io/tokens": "0.9.0",
"@leather.io/ui": "1.17.1",
Expand Down
50 changes: 42 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';

import { useGetInscriptionTextContentQuery } from '@leather.io/query';
import { OrdinalAvatarIcon } from '@leather.io/ui';

Expand All @@ -23,6 +25,7 @@ export function InscriptionText({

return (
<CollectibleText
data-testid={SendCryptoAssetSelectors.Inscription}
icon={<OrdinalAvatarIcon size="lg" />}
key={inscriptionNumber}
onClickCallToAction={onClickCallToAction}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';

import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { Box, Stack, styled } from 'leather-styles/jsx';
import { token } from 'leather-styles/tokens';
import { useHover } from 'use-events';
Expand All @@ -26,6 +27,7 @@ export function CollectibleItemLayout({
showBorder,
subtitle,
title,
...rest
}: CollectibleItemLayoutProps) {
const [isHovered, bind] = useHover();

Expand All @@ -42,6 +44,7 @@ export function CollectibleItemLayout({
p="space.01"
textAlign="inherit"
width="100%"
{...rest}
{...bind}
>
<Box height="0px" position="relative" pb="100%">
Expand Down Expand Up @@ -88,6 +91,7 @@ export function CollectibleItemLayout({
{onClickSend ? (
<Box p="space.02">
<styled.button
data-testid={SendCryptoAssetSelectors.InscriptionSendButton}
_focus={{ clipPath: 'none', outline: 'focus' }}
_hover={{ bg: 'ink.background-primary' }}
bg="ink.background-primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,34 +51,42 @@ export function useSendInscriptionForm() {
setIsCheckingFees(true);

try {
// Check tx with lowest fee for errors before routing and
// generating the final transaction with the chosen fee to send
const resp = coverFeeFromAdditionalUtxos(values);

if (!resp) {
setShowError(
'Insufficient funds to cover fee. Deposit some BTC to your Native Segwit address.'
);
return;
}

if (Number(inscription.offset) !== 0) {
setShowError('Sending inscriptions at non-zero offsets is unsupported');
setShowError(FormErrorMessages.NonZeroOffsetInscription);
return;
}

const numInscriptionsOnUtxo = getNumberOfInscriptionOnUtxo(utxo.txid, utxo.vout);
if (numInscriptionsOnUtxo > 1) {
setShowError('Sending inscription from utxo with multiple inscriptions is unsupported');
setShowError(FormErrorMessages.UtxoWithMultipleInscriptions);
return;
}

// Check tx with lowest fee for errors before routing and
// generating the final transaction with the chosen fee to send
const resp = coverFeeFromAdditionalUtxos(values);

if (!resp) {
setShowError(FormErrorMessages.InsufficientFundsToCoverFee);
return;
}

navigate(
`/${RouteUrls.SendOrdinalInscription}/${RouteUrls.SendOrdinalInscriptionChooseFee}`,
{
state: {
inscription,
recipient: values.recipient,
utxo,
backgroundLocation: { pathname: RouteUrls.Home },
},
}
);
} catch (error) {
void analytics.track('ordinals_dot_com_unavailable', { error });

if (error instanceof InsufficientFundsError) {
setShowError(
'Insufficient funds to cover fee. Deposit some BTC to your Native Segwit address.'
);
setShowError(FormErrorMessages.InsufficientFundsToCoverFee);
return;
}

Expand All @@ -90,18 +98,6 @@ export function useSendInscriptionForm() {
} finally {
setIsCheckingFees(false);
}

navigate(
`/${RouteUrls.SendOrdinalInscription}/${RouteUrls.SendOrdinalInscriptionChooseFee}`,
{
state: {
inscription,
recipient: values.recipient,
utxo,
backgroundLocation: { pathname: RouteUrls.Home },
},
}
);
},

async reviewTransaction(
Expand Down
16 changes: 14 additions & 2 deletions src/app/pages/send/ordinal-inscription/send-inscription-form.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useNavigate } from 'react-router-dom';

import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { Form, Formik } from 'formik';
import { Box, Flex } from 'leather-styles/jsx';

Expand All @@ -8,6 +9,7 @@ import { Button, OrdinalAvatarIcon, Sheet, SheetHeader } from '@leather.io/ui';
import { RouteUrls } from '@shared/route-urls';

import { ErrorLabel } from '@app/components/error-label';
import { TextInputFieldError } from '@app/components/field-error';
import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview';
import { InscriptionPreviewCard } from '@app/components/inscription-preview-card/inscription-preview-card';

Expand Down Expand Up @@ -44,7 +46,12 @@ export function SendInscriptionForm() {
isShowing
onClose={() => navigate(RouteUrls.Home)}
footer={
<Button onClick={() => props.handleSubmit()} type="submit" fullWidth>
<Button
data-testid={SendCryptoAssetSelectors.PreviewSendTxBtn}
onClick={() => props.handleSubmit()}
type="submit"
fullWidth
>
Continue
</Button>
}
Expand All @@ -64,9 +71,14 @@ export function SendInscriptionForm() {
label="To"
placeholder="Enter recipient address"
/>
<TextInputFieldError name={recipientFieldName} />
</Flex>
</Box>
{currentError && <ErrorLabel>{currentError}</ErrorLabel>}
{currentError && (
<ErrorLabel data-testid={SendCryptoAssetSelectors.FormFieldInputErrorLabel}>
{currentError}
</ErrorLabel>
)}
</Box>
</SendInscriptionFormLoader>
</Sheet>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useLocation, useNavigate } from 'react-router-dom';

import { bytesToHex } from '@noble/hashes/utils';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { Box, Flex, Stack } from 'leather-styles/jsx';
import get from 'lodash.get';

Expand Down Expand Up @@ -75,6 +76,7 @@ export function SendInscriptionReview() {
onClose={() => navigate(RouteUrls.Home)}
>
<Card
dataTestId={SendCryptoAssetSelectors.ConfirmationDetails}
border="unset"
contentStyle={{
p: 'space.00',
Expand Down Expand Up @@ -109,7 +111,11 @@ export function SendInscriptionReview() {
px="space.06"
>
<Stack width="100%" mb="36px">
<InfoCardRow title="To" value={<FormAddressDisplayer address={recipient} />} />
<InfoCardRow
data-testid={SendCryptoAssetSelectors.ConfirmationDetailsRecipient}
title="To"
value={<FormAddressDisplayer address={recipient} />}
/>
<InfoCardSeparator />
{arrivesIn && <InfoCardRow title="Estimated confirmation time" value={arrivesIn} />}
<InfoCardRow title="Fee" value={feeRowValue} />
Expand Down
4 changes: 4 additions & 0 deletions src/shared/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export enum FormErrorMessages {
MustSelectAsset = 'Select a valid token to transfer',
SameAddress = 'Cannot send to yourself',
TooMuchPrecision = 'Token can only have {decimals} decimals',

NonZeroOffsetInscription = 'Sending inscriptions at non-zero offsets is unsupported',
UtxoWithMultipleInscriptions = 'Sending inscription from utxo with multiple inscriptions is unsupported',
InsufficientFundsToCoverFee = 'Insufficient funds to cover fee. Deposit some BTC to your Native Segwit address.',
}
9 changes: 9 additions & 0 deletions tests/mocks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ export const STANDARD_BIP_FAKE_MNEMONIC =
export const TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = 'bc1q530dz4h80kwlzywlhx2qn0k6vdtftd93c499yq';
export const TEST_ACCOUNT_1_TAPROOT_ADDRESS =
'bc1putuzj9lyfcm8fef9jpy85nmh33cxuq9u6wyuk536t9kemdk37yjqmkc0pg';
export const TEST_ACCOUNT_2_TAPROOT_ADDRESS =
'bc1pmk2sacpfyy4v5phl8tq6eggu4e8laztep7fsgkkx0nc6m9vydjesaw0g2r';

export const TEST_TESNET_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS =
'tb1q4qgnjewwun2llgken94zqjrx5kpqqycaz5522d';

export const TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS = 'tb1qr8me8t9gu9g6fu926ry5v44yp0wyljrespjtnz';

export const TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS =
'tb1pve00jmp43whpqj2wpcxtc7m8wqhz0azq689y4r7h8tmj8ltaj87qj2nj6w';

// Stacks test addresses
export const TEST_ACCOUNT_1_STX_ADDRESS = 'SPS8CKF63P16J28AYF7PXW9E5AACH0NZNTEFWSFE';
export const TEST_ACCOUNT_2_STX_ADDRESS = 'SPXH3HNBPM5YP15VH16ZXZ9AX6CK289K3MCXRKCB';
Expand Down
27 changes: 27 additions & 0 deletions tests/mocks/mock-inscriptions-bis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Page } from '@playwright/test';

import { BESTINSLOT_API_BASE_URL_TESTNET } from '@leather.io/models';
import { type BestInSlotInscriptionResponse } from '@leather.io/query';

import { TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS } from './constants';

export async function mockTestnetTestAccountInscriptionsRequests(
page: Page,
inscriptions: BestInSlotInscriptionResponse[]
) {
await page.route(`${BESTINSLOT_API_BASE_URL_TESTNET}/wallet/inscriptions_batch`, async route => {
const request = route.request();
const data = request.postData();
const requestBody = data ? JSON.parse(data) : {};

if (requestBody.addresses?.includes(TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS)) {
await route.fulfill({
json: { block_height: 859832, data: inscriptions },
});
return;
}
await route.fulfill({
json: { block_height: 859832, data: [] },
});
});
}
10 changes: 10 additions & 0 deletions tests/mocks/mock-utxos.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Page } from '@playwright/test';

import { BITCOIN_API_BASE_URL_TESTNET } from '@leather.io/models';

import { TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS } from './constants';

export const mockUtxos = [
Expand Down Expand Up @@ -129,3 +131,11 @@ export async function mockMainnetTestAccountBitcoinRequests(page: Page) {
),
]);
}

export async function mockTestnetTestAccountEmptyUtxosRequests(page: Page) {
await page.route(`${BITCOIN_API_BASE_URL_TESTNET}/address/**/utxo`, route =>
route.fulfill({
json: [],
})
);
}
Loading

0 comments on commit 54fce3e

Please sign in to comment.