Skip to content

Commit

Permalink
fix: sbtc swap updates and bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
fbwoolf committed Dec 21, 2024
1 parent 3b22334 commit 1ed6f9c
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import SbtcAvatarIconSrc from '@assets/avatars/sbtc-avatar-icon.png';
import { HStack } from 'leather-styles/jsx';

import { Avatar, Caption, Title } from '@leather.io/ui';
import { Avatar, Caption, Link, Title } from '@leather.io/ui';
import { truncateMiddle } from '@leather.io/utils';

import { analytics } from '@shared/utils/analytics';

import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-link';
import type { SbtcDepositInfo, SbtcStatus } from '@app/query/sbtc/sbtc-deposits.query';
import { openInNewTab } from '@app/common/utils/open-in-new-tab';
import { SbtcDeposit, SbtcStatus } from '@app/query/sbtc/sbtc-deposits.query';

import { TransactionItemLayout } from '../transaction-item/transaction-item.layout';

Expand All @@ -17,37 +19,61 @@ function getDepositStatus(status: SbtcStatus) {
return 'Pending deposit';
case 'accepted':
return 'Pending mint';
case 'confirmed':
return 'Done';
case 'failed':
return 'Failed';
case 'confirmed':
default:
return '';
}
}

function getDepositStatusTextColor(status: SbtcStatus) {
switch (status) {
case 'pending':
case 'reprocessing':
case 'accepted':
return 'yellow.action-primary-default';
case 'failed':
return 'red.action-primary-default';
case 'confirmed':
default:
return '';
}
}

const sbtcReclaimUrl = 'https://app.stacks.co/reclaim?depositTxId=';

interface SbtcDepositTransactionItemProps {
deposit: SbtcDepositInfo;
deposit: SbtcDeposit;
}
export function SbtcDepositTransactionItem({ deposit }: SbtcDepositTransactionItemProps) {
const { handleOpenBitcoinTxLink: handleOpenTxLink } = useBitcoinExplorerLink();
const { bitcoinTxid, status } = deposit;
const depositFailed = status === 'failed';

const openTxLink = () => {
function openTxLink() {
void analytics.track('view_bitcoin_transaction');
handleOpenTxLink({ txid: deposit.bitcoinTxid });
};
handleOpenTxLink({ txid: bitcoinTxid });
}

function openReclaimLink() {
return openInNewTab(`${sbtcReclaimUrl}${bitcoinTxid}`);
}

return (
<TransactionItemLayout
openTxLink={openTxLink}
txCaption={truncateMiddle(deposit.bitcoinTxid, 4)}
openTxLink={!depositFailed ? openTxLink : () => {}}
txCaption={truncateMiddle(bitcoinTxid, 4)}
txIcon={
<Avatar.Root>
<Avatar.Image alt="ST" src={SbtcAvatarIconSrc} />
</Avatar.Root>
}
txStatus={
<Caption color="yellow.action-primary-default">{getDepositStatus(deposit.status)}</Caption>
<HStack>
<Caption color={getDepositStatusTextColor(status)}>{getDepositStatus(status)}</Caption>
{depositFailed && <Link onClick={openReclaimLink}>Reclaim</Link>}
</HStack>
}
txTitle={<Title textStyle="label.02">BTC → sBTC</Title>}
// Api is only returning 0 right now
Expand Down
28 changes: 23 additions & 5 deletions src/app/features/activity-list/activity-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@ import {

import { LoadingSpinner } from '@app/components/loading-spinner';
import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query';
import { useSbtcPendingDeposits } from '@app/query/sbtc/sbtc-deposits.query';
import {
useSbtcConfirmedDeposits,
useSbtcFailedDeposits,
useSbtcPendingDeposits,
} from '@app/query/sbtc/sbtc-deposits.query';
import { useZeroIndexTaprootAddress } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useUpdateSubmittedTransactions } from '@app/store/submitted-transactions/submitted-transactions.hooks';
import { useSubmittedTransactions } from '@app/store/submitted-transactions/submitted-transactions.selectors';

import { convertBitcoinTxsToListType, convertStacksTxsToListType } from './activity-list.utils';
import {
convertBitcoinTxsToListType,
convertSbtcDepositToListType,
convertStacksTxsToListType,
} from './activity-list.utils';
import { NoAccountActivity } from './components/no-account-activity';
import { PendingTransactionList } from './components/pending-transaction-list/pending-transaction-list';
import { SubmittedTransactionList } from './components/submitted-transaction-list/submitted-transaction-list';
Expand Down Expand Up @@ -64,8 +72,12 @@ export function ActivityList() {
[nsPendingTxs, trPendingTxs]
);

const { isLoading: isLoadingSbtcDeposits, pendingSbtcDeposits } =
const { isLoading: isLoadingSbtcPendingDeposits, pendingSbtcDeposits } =
useSbtcPendingDeposits(stxAddress);
const { isLoading: isLoadingSbtcConfirmedDeposits, confirmedSbtcDeposits } =
useSbtcConfirmedDeposits(stxAddress);
const { isLoading: isLoadingSbtcFailedDeposits, failedSbtcDeposits } =
useSbtcFailedDeposits(stxAddress);

const { isLoading: isLoadingStacksTransactions, data: stacksTransactionsWithTransfers } =
useGetAccountTransactionsWithTransfersQuery(stxAddress);
Expand All @@ -85,7 +97,9 @@ export function ActivityList() {
isLoadingTrBitcoinTransactions ||
isLoadingStacksTransactions ||
isLoadingStacksPendingTransactions ||
isLoadingSbtcDeposits;
isLoadingSbtcPendingDeposits ||
isLoadingSbtcConfirmedDeposits ||
isLoadingSbtcFailedDeposits;

const transactionListBitcoinTxs = useMemo(() => {
return convertBitcoinTxsToListType(
Expand Down Expand Up @@ -135,14 +149,18 @@ export function ActivityList() {
{hasPendingTransactions && (
<PendingTransactionList
bitcoinTxs={isBitcoinEnabled ? bitcoinPendingTxs : []}
sBtcDeposits={pendingSbtcDeposits}
sbtcDeposits={pendingSbtcDeposits}
stacksTxs={stacksPendingTransactions}
/>
)}
{hasTransactions && (
<TransactionList
bitcoinTxs={isBitcoinEnabled ? transactionListBitcoinTxs : []}
stacksTxs={transactionListStacksTxs}
sbtcDeposits={convertSbtcDepositToListType([
...confirmedSbtcDeposits,
...failedSbtcDeposits,
])}
currentBitcoinAddress={nsBitcoinAddress}
/>
)}
Expand Down
17 changes: 16 additions & 1 deletion src/app/features/activity-list/activity-list.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { AddressTransactionWithTransfers } from '@stacks/stacks-blockchain-api-t

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

import {
import type { SbtcDeposit } from '@app/query/sbtc/sbtc-deposits.query';

import type {
TransactionListBitcoinTx,
TransactionListSbtcDeposit,
TransactionListStacksTx,
} from './components/transaction-list/transaction-list.model';

Expand All @@ -21,6 +24,13 @@ function createStacksTxTypeWrapper(tx: AddressTransactionWithTransfers): Transac
};
}

function createSbtcDepositTxTypeWrapper(deposit: SbtcDeposit): TransactionListSbtcDeposit {
return {
blockchain: 'bitcoin-stacks',
deposit,
};
}

export function convertBitcoinTxsToListType(txs?: BitcoinTx[]) {
if (!txs) return [];
const confirmedTxs = txs.filter(tx => tx.status.confirmed);
Expand All @@ -31,3 +41,8 @@ export function convertStacksTxsToListType(txs?: AddressTransactionWithTransfers
if (!txs) return [];
return txs.map(tx => createStacksTxTypeWrapper(tx));
}

export function convertSbtcDepositToListType(deposits?: SbtcDeposit[]) {
if (!deposits) return [];
return deposits.map(deposit => createSbtcDepositTxTypeWrapper(deposit));
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import type { BitcoinTx } from '@leather.io/models';
import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item';
import { SbtcDepositTransactionItem } from '@app/components/sbtc-deposit-status-item/sbtc-deposit-status-item';
import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item';
import type { SbtcDepositInfo } from '@app/query/sbtc/sbtc-deposits.query';
import type { SbtcDeposit } from '@app/query/sbtc/sbtc-deposits.query';

import { PendingTransactionListLayout } from './pending-transaction-list.layout';

interface PendingTransactionListProps {
bitcoinTxs: BitcoinTx[];
sBtcDeposits: SbtcDepositInfo[];
sbtcDeposits: SbtcDeposit[];
stacksTxs: MempoolTransaction[];
}
export function PendingTransactionList({
bitcoinTxs,
sBtcDeposits,
sbtcDeposits,
stacksTxs,
}: PendingTransactionListProps) {
return (
<PendingTransactionListLayout>
{bitcoinTxs.map(tx => (
<BitcoinTransactionItem key={tx.txid} transaction={tx} />
))}
{sBtcDeposits.map(deposit => (
{sbtcDeposits.map(deposit => (
<SbtcDepositTransactionItem key={deposit.bitcoinTxid} deposit={deposit} />
))}
{stacksTxs.map(tx => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SbtcDepositTransactionItem } from '@app/components/sbtc-deposit-status-item/sbtc-deposit-status-item';

import { BitcoinTransaction } from './bitcoin-transaction/bitcoin-transaction';
import { StacksTransaction } from './stacks-transaction/stacks-transaction';
import { TransactionListTxs } from './transaction-list.model';
Expand All @@ -11,6 +13,8 @@ export function TransactionListItem({ tx }: TransactionListItemProps) {
return <BitcoinTransaction transaction={tx.transaction} />;
case 'stacks':
return <StacksTransaction transaction={tx.transaction} />;
case 'bitcoin-stacks':
return <SbtcDepositTransactionItem deposit={tx.deposit} />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { AddressTransactionWithTransfers } from '@stacks/stacks-blockchain-api-types';

import type { BitcoinTx, Blockchain } from '@leather.io/models';
import type { BitcoinTx } from '@leather.io/models';

import type { SbtcDeposit } from '@app/query/sbtc/sbtc-deposits.query';

export interface TransactionListBitcoinTx {
blockchain: Extract<Blockchain, 'bitcoin'>;
blockchain: 'bitcoin';
transaction: BitcoinTx;
}

export interface TransactionListStacksTx {
blockchain: Extract<Blockchain, 'stacks'>;
blockchain: 'stacks';
transaction: AddressTransactionWithTransfers;
}

export type TransactionListTxs = TransactionListBitcoinTx | TransactionListStacksTx;
export interface TransactionListSbtcDeposit {
blockchain: 'bitcoin-stacks';
deposit: SbtcDeposit;
}

export type TransactionListTxs =
| TransactionListBitcoinTx
| TransactionListStacksTx
| TransactionListSbtcDeposit;
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,36 @@ import { Box } from 'leather-styles/jsx';
import { useTransactionListRender } from './hooks/use-transaction-list-render';
import { TransactionListItem } from './transaction-list-item';
import { TransactionListLayout } from './transaction-list.layout';
import { TransactionListBitcoinTx, TransactionListStacksTx } from './transaction-list.model';
import type {
TransactionListBitcoinTx,
TransactionListSbtcDeposit,
TransactionListStacksTx,
} from './transaction-list.model';
import { createTxDateFormatList, getTransactionId } from './transaction-list.utils';
import { TransactionsByDateLayout } from './transactions-by-date.layout';

interface TransactionListProps {
bitcoinTxs: TransactionListBitcoinTx[];
stacksTxs: TransactionListStacksTx[];
sbtcDeposits: TransactionListSbtcDeposit[];
currentBitcoinAddress: string;
}

export function TransactionList({
bitcoinTxs,
stacksTxs,
sbtcDeposits,
currentBitcoinAddress,
}: TransactionListProps) {
const { intersectionSentinel, visibleTxsNum } = useTransactionListRender({
currentBitcoinAddress,
});
const txsGroupedByDate = useMemo(
() =>
bitcoinTxs.length || stacksTxs.length ? createTxDateFormatList(bitcoinTxs, stacksTxs) : [],
[bitcoinTxs, stacksTxs]
bitcoinTxs.length || stacksTxs.length || sbtcDeposits.length
? createTxDateFormatList(bitcoinTxs, stacksTxs, sbtcDeposits)
: [],
[bitcoinTxs, sbtcDeposits, stacksTxs]
);

const groupedByDateTxsLength = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { AddressTransactionWithTransfers, Transaction } from '@stacks/stacks-blockchain-api-types';
import dayjs from 'dayjs';

import { TransactionListBitcoinTx, TransactionListStacksTx } from './transaction-list.model';
import type { StacksBlock } from '@app/query/sbtc/get-stacks-block.query';
import type { SbtcDeposit } from '@app/query/sbtc/sbtc-deposits.query';

import type {
TransactionListBitcoinTx,
TransactionListSbtcDeposit,
TransactionListStacksTx,
} from './transaction-list.model';
import { createTxDateFormatList } from './transaction-list.utils';

function createFakeTx(tx: Partial<Transaction>) {
Expand All @@ -17,6 +24,13 @@ function createFakeTx(tx: Partial<Transaction>) {
} as TransactionListStacksTx;
}

function createFakeDeposit(block: Partial<StacksBlock>) {
return {
blockchain: 'bitcoin-stacks',
deposit: { block } as Partial<SbtcDeposit>,
} as TransactionListSbtcDeposit;
}

describe(createTxDateFormatList.name, () => {
test('grouping by date', () => {
const mockBitcoinTx = {
Expand All @@ -26,7 +40,7 @@ describe(createTxDateFormatList.name, () => {
const mockStacksTx = createFakeTx({
burn_block_time_iso: '1991-02-08T13:48:04.699Z',
});
expect(createTxDateFormatList([mockBitcoinTx], [mockStacksTx])).toEqual([
expect(createTxDateFormatList([mockBitcoinTx], [mockStacksTx], [])).toEqual([
{
date: '1991-02-08',
displayDate: 'Feb 8th, 1991',
Expand All @@ -42,7 +56,8 @@ describe(createTxDateFormatList.name, () => {
transaction: { status: { confirmed: true, block_time: dayjs().unix() } },
} as TransactionListBitcoinTx;
const mockStacksTx = createFakeTx({ burn_block_time_iso: today });
const result = createTxDateFormatList([mockBitcoinTx], [mockStacksTx]);
const mockSbtcDeposit = createFakeDeposit({ burn_block_time_iso: today });
const result = createTxDateFormatList([mockBitcoinTx], [mockStacksTx], [mockSbtcDeposit]);
expect(result[0].date).toEqual(today.split('T')[0]);
expect(result[0].displayDate).toEqual('Today');
});
Expand All @@ -55,7 +70,8 @@ describe(createTxDateFormatList.name, () => {
transaction: { status: { confirmed: true, block_time: dayjs().subtract(1, 'day').unix() } },
} as TransactionListBitcoinTx;
const mockStacksTx = createFakeTx({ burn_block_time_iso: yesterday.toISOString() });
const result = createTxDateFormatList([mockBitcoinTx], [mockStacksTx]);
const mockSbtcDeposit = createFakeDeposit({ burn_block_time_iso: yesterday.toISOString() });
const result = createTxDateFormatList([mockBitcoinTx], [mockStacksTx], [mockSbtcDeposit]);
expect(result[0].date).toEqual(yesterday.toISOString().split('T')[0]);
expect(result[0].displayDate).toEqual('Yesterday');
});
Expand All @@ -65,7 +81,8 @@ describe(createTxDateFormatList.name, () => {
date.setFullYear(date.getFullYear());
date.setMonth(6);
const mockStacksTx = createFakeTx({ burn_block_time_iso: date.toISOString() });
const result = createTxDateFormatList([], [mockStacksTx]);
const mockSbtcDeposit = createFakeDeposit({ burn_block_time_iso: date.toISOString() });
const result = createTxDateFormatList([], [mockStacksTx], [mockSbtcDeposit]);
expect(result[0].date).toEqual(date.toISOString().split('T')[0]);
expect(result[0].displayDate).not.toContain(new Date().getFullYear().toString());
});
Expand Down
Loading

0 comments on commit 1ed6f9c

Please sign in to comment.