diff --git a/e2e/bioforest-full-flow.spec.ts b/e2e/bioforest-full-flow.spec.ts
index 05ce11b3a..4cf50f639 100644
--- a/e2e/bioforest-full-flow.spec.ts
+++ b/e2e/bioforest-full-flow.spec.ts
@@ -268,8 +268,8 @@ async function performTransfer(
await continueBtn.click()
await page.waitForTimeout(500)
- // 点击确认转账 (TransferPreviewJob)
- const confirmBtn = page.locator(`[data-testid="confirm-preview-button"], button:has-text("${UI_TEXT.confirm.source}")`).first()
+ // 点击确认转账
+ const confirmBtn = page.locator(`[data-testid="confirm-transfer-button"], button:has-text("${UI_TEXT.confirm.source}")`).first()
await expect(confirmBtn).toBeVisible({ timeout: 5000 })
await confirmBtn.click()
await page.waitForTimeout(500)
diff --git a/e2e/bioforest-real-transfer.spec.ts b/e2e/bioforest-real-transfer.spec.ts
index 1a9b4c9e3..9752e413f 100644
--- a/e2e/bioforest-real-transfer.spec.ts
+++ b/e2e/bioforest-real-transfer.spec.ts
@@ -178,7 +178,7 @@ async function doTransfer(page: Page, toAddress: string, amount: string, needPay
await expect(continueBtn).toBeEnabled({ timeout: 15000 })
await continueBtn.click()
- await page.locator('[data-testid="confirm-preview-button"]').click()
+ await page.locator('[data-testid="confirm-transfer-button"]').click()
// 验证钱包锁
const patternInput = page.locator('[data-testid="wallet-pattern-input"]')
diff --git a/e2e/bioforest-transfer.spec.ts b/e2e/bioforest-transfer.spec.ts
index 8a4793e4a..b79f2961c 100644
--- a/e2e/bioforest-transfer.spec.ts
+++ b/e2e/bioforest-transfer.spec.ts
@@ -201,9 +201,9 @@ describeOrSkip('BioForest 转账测试', () => {
await continueBtn.click()
console.log(' ✅ 继续到确认页')
- // 7. 确认转账(TransferPreviewJob)
+ // 7. 确认转账
console.log('7. 确认转账...')
- const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')
+ const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
await expect(confirmBtn).toBeVisible({ timeout: 5000 })
await confirmBtn.click()
console.log(' ✅ 点击确认')
diff --git a/e2e/send-transaction.mock.spec.ts b/e2e/send-transaction.mock.spec.ts
index 6ec52f51f..c56109e18 100644
--- a/e2e/send-transaction.mock.spec.ts
+++ b/e2e/send-transaction.mock.spec.ts
@@ -344,14 +344,14 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await page.waitForTimeout(500)
// 检查确认弹窗内容
- const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')
- const cancelBtn = page.locator('[data-testid="cancel-preview-button"]')
+ const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
+ const cancelBtn = page.locator('[data-testid="cancel-transfer-button"]')
// 至少一个按钮应该可见(确认或取消)
const hasConfirmUI = await confirmBtn.isVisible() || await cancelBtn.isVisible()
if (hasConfirmUI) {
- console.log('TransferPreviewJob opened successfully')
+ console.log('TransferConfirmJob opened successfully')
// 截图
await expect(page).toHaveScreenshot('send-confirm-job.png')
@@ -365,7 +365,7 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await expect(page.locator('[data-testid="send-continue-button"]')).toBeVisible()
}
} else {
- console.log('TransferPreviewJob may not have opened - check mock configuration')
+ console.log('TransferConfirmJob may not have opened - check mock configuration')
}
} else {
console.log('Continue button not enabled - mock service may not be configured correctly')
@@ -390,7 +390,7 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await continueBtn.click()
await page.waitForTimeout(500)
- const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')
+ const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
if (await confirmBtn.isVisible()) {
await confirmBtn.click()
diff --git a/src/components/wallet/wallet-address-portfolio-from-provider.tsx b/src/components/wallet/wallet-address-portfolio-from-provider.tsx
deleted file mode 100644
index 9a38fd0a4..000000000
--- a/src/components/wallet/wallet-address-portfolio-from-provider.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { useMemo } from 'react'
-import { WalletAddressPortfolioView, type WalletAddressPortfolioViewProps } from './wallet-address-portfolio-view'
-import type { ChainType } from '@/stores'
-import { ChainProviderGate, useChainProvider } from '@/contexts'
-import type { TokenInfo } from '@/components/token/token-item'
-import { toTransactionInfoList } from '@/components/transaction/adapters'
-
-export interface WalletAddressPortfolioFromProviderProps {
- chainId: ChainType
- address: string
- chainName?: string
- onTokenClick?: WalletAddressPortfolioViewProps['onTokenClick']
- onTransactionClick?: WalletAddressPortfolioViewProps['onTransactionClick']
- className?: string
- testId?: string
-}
-
-/**
- * 从 Provider 获取地址资产组合(内部实现)
- *
- * 使用 ChainProvider 响应式 API 获取数据,复用 WalletAddressPortfolioView 展示。
- */
-function WalletAddressPortfolioFromProviderInner({
- chainId,
- address,
- chainName,
- onTokenClick,
- onTransactionClick,
- className,
- testId,
-}: WalletAddressPortfolioFromProviderProps) {
- const chainProvider = useChainProvider()
-
- // 使用新的响应式 API
- const { data: tokens = [], isLoading: tokensLoading } = chainProvider.allBalances.useState(
- { address },
- { enabled: !!address }
- )
-
- const { data: txResult, isLoading: transactionsLoading } = chainProvider.transactionHistory.useState(
- { address, limit: 50 },
- { enabled: !!address }
- )
-
- // 转换为 TokenInfo 格式
- const tokenInfoList: TokenInfo[] = useMemo(() => {
- return tokens.map((token) => ({
- symbol: token.symbol,
- name: token.name,
- chain: chainId,
- balance: token.amount.toFormatted(),
- decimals: token.decimals,
- fiatValue: undefined,
- change24h: 0,
- icon: token.icon,
- }))
- }, [tokens, chainId])
-
- // 转换交易历史格式
- const transactions = useMemo(() => {
- if (!txResult) return []
- return toTransactionInfoList(txResult, chainId)
- }, [txResult, chainId])
-
- return (
-
- )
-}
-
-/**
- * 从 Provider 获取地址资产组合
- *
- * 使用 ChainProviderGate 确保 ChainProvider 可用,再使用响应式 API 获取数据。
- * 适用于 Stories 测试和任意地址查询场景。
- */
-export function WalletAddressPortfolioFromProvider(props: WalletAddressPortfolioFromProviderProps) {
- return (
-
-
-
Chain not supported: {props.chainId}
-
-
- }
- >
-
-
- )
-}
diff --git a/src/components/wallet/wallet-address-portfolio.stories.tsx b/src/components/wallet/wallet-address-portfolio.stories.tsx
index 6f04f298a..792767e79 100644
--- a/src/components/wallet/wallet-address-portfolio.stories.tsx
+++ b/src/components/wallet/wallet-address-portfolio.stories.tsx
@@ -1,19 +1,11 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
-import type { ReactRenderer } from '@storybook/react';
-import type { DecoratorFunction } from 'storybook/internal/types';
-import { useEffect, useState, useMemo, useCallback } from 'react';
import { expect, waitFor, within } from '@storybook/test';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WalletAddressPortfolioView } from './wallet-address-portfolio-view';
-import { WalletAddressPortfolioFromProvider } from './wallet-address-portfolio-from-provider';
-import { TokenIconProvider } from './token-icon';
-import { chainConfigActions, chainConfigStore, useChainConfigState, useChainConfigs } from '@/stores/chain-config';
-import { clearProviderCache } from '@/services/chain-adapter';
-import { resolveAssetUrl } from '@/lib/asset-url';
import { Amount } from '@/types/amount';
import type { TokenInfo } from '@/components/token/token-item';
import type { TransactionInfo, TransactionType } from '@/components/transaction/transaction-item';
-import type { ChainConfig } from '@/services/chain-config';
+
+// ==================== Mock 数据 ====================
const mockTokens: TokenInfo[] = [
{ symbol: 'BFT', name: 'BFT', balance: '1234.56789012', decimals: 8, chain: 'bfmeta' },
@@ -44,436 +36,56 @@ const mockTransactions: TransactionInfo[] = [
},
];
-// 基于 Bitcoin mempool.space 真实数据格式创建的 mock 交易
-// 参考: https://mempool.space/api/address/1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa/txs
-const mockBitcoinTransactions: TransactionInfo[] = [
- {
- id: '78a44e6e62bf7638065bf58327c8486217dbf84bba617def8f8a2816a23e14c9',
- type: 'receive' as TransactionType,
- status: 'pending',
- amount: Amount.fromRaw('546', 8, 'BTC'),
- symbol: 'BTC',
- address: 'bc1pl4qpz24u7zf6zn7lckdckglns02xghrh4jw3qeh2w37x3m6k657qv6pqw7',
- timestamp: new Date(Date.now() - 600000), // 10 min ago
- chain: 'bitcoin',
- },
- {
- id: 'd274d00350d384f443cb1e42defdd7e12f350aee37813d305b6bb9468270de19',
- type: 'receive' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('546', 8, 'BTC'),
- symbol: 'BTC',
- address: 'bc1py4h77ccc0yalhrv2w8h5l5htw2t2up7nhcmg5t89ndgkjhpxek3qz3dsgc',
- timestamp: new Date(1767688327000),
- chain: 'bitcoin',
- },
- {
- id: 'c11dfe6e1033eb354c6bf7b3428f9290d635cefbe16876b86a8ce84be8c5637d',
- type: 'receive' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('546', 8, 'BTC'),
- symbol: 'BTC',
- address: 'bc1p75kfwfe6se8uztt67nat7fev50ydly9lmcrdj6gtalz6zmsjachqtryrtc',
- timestamp: new Date(1767682433000),
- chain: 'bitcoin',
- },
- {
- id: 'd8773c23582a0bf0fe7f640fd1053c14c5ebf3782fed994bd9cd2aed7d19dedd',
- type: 'receive' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('12168', 8, 'BTC'),
- symbol: 'BTC',
- address: 'bc1ppcagzftu7w6pl7saqvs4nvtpwpr2g7ja96tdylt3lz775yjkkcsqlu7exa',
- timestamp: new Date(1767681823000),
- chain: 'bitcoin',
- },
-];
-
-// 基于 TronGrid API 真实数据格式创建的 mock 交易
-// 参考: https://api.trongrid.io/v1/accounts/TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9/transactions
-const mockTronTransactions: TransactionInfo[] = [
- {
- id: 'caacd034fd600a9a66cb9c841f39b81101e97e23068c8d73152f8741654139e1',
- type: 'send' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('149000000', 6, 'TRX'), // 149 TRX
- symbol: 'TRX',
- address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3',
- timestamp: new Date(1767691140000),
- chain: 'tron',
- },
- {
- id: '2b8d0c9e7f3a1b5e4d6c8a9f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d',
- type: 'receive' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('500000000', 6, 'TRX'), // 500 TRX
- symbol: 'TRX',
- address: 'TAnahWWRPm6jhiYR6gMCE3eLDqL8LfCnVE',
- timestamp: new Date(1767680000000),
- chain: 'tron',
- },
- {
- id: '3c9e1d0f8a2b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d',
- type: 'send' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('1000000000', 6, 'TRX'), // 1000 TRX
- symbol: 'TRX',
- address: 'TRkJg1B9WgM8uXzthJJhBhX7K1GBqH9dXb',
- timestamp: new Date(1767670000000),
- chain: 'tron',
- },
-];
-
-// 基于 Etherscan API 真实数据格式创建的 mock 交易 (Vitalik's wallet)
const mockEthereumTransactions: TransactionInfo[] = [
{
id: '0x1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890',
type: 'send' as TransactionType,
status: 'confirmed',
- amount: Amount.fromRaw('1000000000000000000', 18, 'ETH'), // 1 ETH
+ amount: Amount.fromRaw('1000000000000000000', 18, 'ETH'),
symbol: 'ETH',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38',
- timestamp: new Date(Date.now() - 7200000), // 2 hours ago
+ timestamp: new Date(Date.now() - 7200000),
chain: 'ethereum',
},
{
id: '0x2b3c4d5e6f78901abcdef2345678901abcdef2345678901abcdef2345678901',
type: 'receive' as TransactionType,
status: 'confirmed',
- amount: Amount.fromRaw('5000000000000000000', 18, 'ETH'), // 5 ETH
+ amount: Amount.fromRaw('5000000000000000000', 18, 'ETH'),
symbol: 'ETH',
address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE',
- timestamp: new Date(Date.now() - 86400000), // 1 day ago
+ timestamp: new Date(Date.now() - 86400000),
chain: 'ethereum',
},
+];
+
+const mockTronTransactions: TransactionInfo[] = [
{
- id: '0x3c4d5e6f789012abcdef3456789012abcdef3456789012abcdef3456789012',
+ id: 'caacd034fd600a9a66cb9c841f39b81101e97e23068c8d73152f8741654139e1',
type: 'send' as TransactionType,
status: 'confirmed',
- amount: Amount.fromRaw('100000000', 6, 'USDC'), // 100 USDC
- symbol: 'USDC',
- address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
- timestamp: new Date(Date.now() - 172800000), // 2 days ago
- chain: 'ethereum',
+ amount: Amount.fromRaw('149000000', 6, 'TRX'),
+ symbol: 'TRX',
+ address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3',
+ timestamp: new Date(Date.now() - 3600000),
+ chain: 'tron',
},
];
-// BSC 交易 mock
const mockBinanceTransactions: TransactionInfo[] = [
{
id: '0xbsc1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab',
type: 'receive' as TransactionType,
status: 'confirmed',
- amount: Amount.fromRaw('10000000000000000000', 18, 'BNB'), // 10 BNB
+ amount: Amount.fromRaw('10000000000000000000', 18, 'BNB'),
symbol: 'BNB',
address: '0x8894E0a0c962CB723c1976a4421c95949bE2D4E3',
timestamp: new Date(Date.now() - 3600000),
chain: 'binance',
},
- {
- id: '0xbsc2345678901abcdef2345678901abcdef2345678901abcdef2345678901bc',
- type: 'send' as TransactionType,
- status: 'confirmed',
- amount: Amount.fromRaw('5000000000000000000', 18, 'BNB'), // 5 BNB
- symbol: 'BNB',
- address: '0x28C6c06298d514Db089934071355E5743bf21d60',
- timestamp: new Date(Date.now() - 14400000), // 4 hours ago
- chain: 'binance',
- },
];
-function ChainConfigProvider({ children }: { children: React.ReactNode }) {
- const state = useChainConfigState();
- const [initStarted, setInitStarted] = useState(false);
-
- useEffect(() => {
- if (!initStarted && !state.snapshot && !state.isLoading) {
- setInitStarted(true);
- clearProviderCache();
- chainConfigActions.initialize();
- }
- }, [initStarted, state.snapshot, state.isLoading]);
-
- if (state.error) {
- return (
-
-
-
Chain config error
-
{state.error}
-
-
- );
- }
-
- if (!state.snapshot) {
- return (
-
-
-
Loading chain configuration...
-
-
- );
- }
-
- return <>{children}>;
-}
-
-function TokenIconProviderWrapper({ children }: { children: React.ReactNode }) {
- const configs = useChainConfigs();
-
- const resolvedConfigs = useMemo(() => {
- return configs.map((config) => ({
- ...config,
- tokenIconBase: config.tokenIconBase?.map(resolveAssetUrl),
- }));
- }, [configs]);
-
- const getTokenIconBases = useCallback(
- (chainId: string) => resolvedConfigs.find((c) => c.id === chainId)?.tokenIconBase ?? [],
- [resolvedConfigs],
- );
-
- return (
-
- {children}
-
- );
-}
-
-const REAL_ADDRESSES: Record = {
- ethereum: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
- tron: 'TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9',
- bitcoin: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
- binance: '0x8894E0a0c962CB723c1976a4421c95949bE2D4E3',
- bfmeta: 'bCfAynSAKhzgKLi3BXyuh5k22GctLR72j',
-};
-
-function formatApiLabel(api: ChainConfig['apis'][number]): string {
- const apiKeyEnv = api.config && 'apiKeyEnv' in api.config ? (api.config.apiKeyEnv as string | undefined) : undefined;
- if (apiKeyEnv) return `${api.type} (${apiKeyEnv})`;
- return api.type;
-}
-
-function generateSingleApiConfigs(baseConfig: ChainConfig): ChainConfig[] {
- return baseConfig.apis.map((api, index) => ({
- ...baseConfig,
- id: `${baseConfig.id}-api-${index}-${api.type}`,
- name: `${baseConfig.name} (${api.type})`,
- apis: [api],
- enabled: true,
- source: 'manual' as const,
- }));
-}
-
-function DynamicCompareConfigInjector({ chainId }: { chainId: string }) {
- const state = useChainConfigState();
- const [injected, setInjected] = useState(false);
-
- useEffect(() => {
- if (!state.snapshot) return;
- if (injected) return;
-
- const baseConfig = state.snapshot.configs.find((c) => c.id === chainId);
- if (!baseConfig) return;
-
- const alreadyInjected = state.snapshot.configs.some((c) => c.id === `${chainId}-api-0-${baseConfig.apis[0]?.type}`);
- if (alreadyInjected) {
- setInjected(true);
- return;
- }
-
- const singleApiConfigs = generateSingleApiConfigs(baseConfig);
-
- chainConfigStore.setState((prev) => {
- if (!prev.snapshot) return prev;
- return {
- ...prev,
- snapshot: {
- ...prev.snapshot,
- configs: [...prev.snapshot.configs, ...singleApiConfigs],
- },
- };
- });
-
- clearProviderCache();
- setInjected(true);
- }, [state.snapshot, injected, chainId]);
-
- return null;
-}
-
-function DynamicProviderPanel({
- api,
- baseConfig,
- index,
- address,
-}: {
- api: ChainConfig['apis'][number];
- baseConfig: ChainConfig;
- index: number;
- address: string;
-}) {
- const dynamicChainId = `${baseConfig.id}-api-${index}-${api.type}`;
- const testId = `cmp-${baseConfig.id}-${api.type}`;
-
- return (
-
-
-
-
-
{api.type}
-
{api.endpoint}
-
-
#{index}
-
-
{formatApiLabel(api)}
-
-
-
- );
-}
-
-function DynamicCompareProvidersGrid({ chainId }: { chainId: string }) {
- const state = useChainConfigState();
- const baseConfig = state.snapshot?.configs.find((c) => c.id === chainId);
-
- if (!baseConfig) {
- return Chain config not found: {chainId}
;
- }
-
- const address = REAL_ADDRESSES[chainId] ?? '';
-
- return (
-
-
-
{baseConfig.name}
-
- Comparing {baseConfig.apis.length} provider(s) from default-chains.json
-
-
{address}
-
-
- {baseConfig.apis.map((api, index) => (
-
- ))}
-
-
- );
-}
-
-const createQueryClient = () =>
- new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- staleTime: 0,
- },
- },
- });
-
-const withChainConfig: DecoratorFunction = (Story) => (
-
-
-
-
-
-
-
-
-
-);
-
-const createCompareDecorator = (chainId: string): DecoratorFunction => (Story) => (
-
-
-
-
-
-
-
-
-
-
-);
-
-function hasPositiveNumber(text: string): boolean {
- const matches = text.match(/\d+(?:\.\d+)?/g);
- if (!matches) return false;
- for (const value of matches) {
- const parsed = Number(value);
- if (Number.isFinite(parsed) && parsed > 0) return true;
- }
- return false;
-}
-
-async function verifyNonEmptyAssetsAndHistory(canvas: ReturnType, testId: string): Promise {
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId(testId);
- expect(portfolio).toBeVisible();
- },
- { timeout: 25_000 },
- );
-
- await waitFor(
- () => {
- const tokenList = canvas.queryByTestId(`${testId}-token-list`);
- expect(tokenList).not.toBeNull();
- const tokenItems = tokenList?.querySelectorAll('[data-testid^="token-item-"]') ?? [];
- expect(tokenItems.length).toBeGreaterThan(0);
- const tokenText = tokenItems[0]?.textContent ?? '';
- expect(hasPositiveNumber(tokenText)).toBe(true);
-
- const tokenEmpty = canvas.queryByTestId(`${testId}-token-list-empty`);
- expect(tokenEmpty).toBeNull();
- },
- { timeout: 25_000 },
- );
-
- const historyTab = canvas.getByTestId(`${testId}-tabs-tab-history`);
- historyTab.click();
-
- await waitFor(
- () => {
- const txList = canvas.queryByTestId(`${testId}-transaction-list`);
- expect(txList).not.toBeNull();
-
- const txEmpty = canvas.queryByTestId(`${testId}-transaction-list-empty`);
- expect(txEmpty).toBeNull();
- },
- { timeout: 25_000 },
- );
-}
-
-async function expectAnyProviderPanelOk(options: {
- canvas: ReturnType;
- label: string;
- testIds: string[];
-}): Promise {
- const errors: string[] = [];
- for (const testId of options.testIds) {
- try {
- await verifyNonEmptyAssetsAndHistory(options.canvas, testId);
- return;
- } catch (error) {
- errors.push(`${testId}: ${error instanceof Error ? error.message : String(error)}`);
- }
- }
-
- throw new Error(`No provider panel returned non-empty data for ${options.label}. Details: ${errors.join(' | ')}`);
-}
+// ==================== Meta ====================
const meta = {
title: 'Wallet/WalletAddressPortfolio',
@@ -487,6 +99,8 @@ const meta = {
export default meta;
type Story = StoryObj;
+// ==================== 基础状态 Stories ====================
+
export const Default: Story = {
args: {
chainId: 'bfmeta',
@@ -535,304 +149,70 @@ export const Empty: Story = {
},
};
-export const RealDataBfmeta: Story = {
- name: 'Real Data: biochain-bfmeta',
- decorators: [withChainConfig],
- parameters: {
- chromatic: { delay: 5000 },
- docs: {
- description: {
- story: 'Fetches real token balances and transactions from BFMeta chain using the actual chainProvider API.',
- },
- },
- },
- render: () => (
-
- ),
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
-
- // Wait for data to load - check component renders without error
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId('bfmeta-portfolio');
- expect(portfolio).toBeVisible();
-
- // Check loading finished (either has tokens, empty state, or loading skeleton stopped)
- const tokenList = canvas.queryByTestId('bfmeta-portfolio-token-list');
- const tokenEmpty = canvas.queryByTestId('bfmeta-portfolio-token-list-empty');
- const loading = portfolio.querySelector('.animate-pulse');
-
- // Should have either: token list, empty state, or still loading
- expect(tokenList || tokenEmpty || loading).not.toBeNull();
-
- // If token list exists, verify it has items
- if (tokenList) {
- const tokenItems = tokenList.querySelectorAll('[data-testid^="token-item-"]');
- expect(tokenItems.length).toBeGreaterThan(0);
- }
- },
- { timeout: 15000 },
- );
- },
-};
-
-export const RealDataEthereum: Story = {
- name: 'Real Data: eth-eth',
- decorators: [withChainConfig],
- parameters: {
- chromatic: { delay: 5000 },
- docs: {
- description: {
- story:
- 'Fetches real token balances and transactions from Ethereum mainnet using blockscout API. Uses Vitalik address for real ETH transfers.',
- },
- },
- },
- render: () => (
-
- ),
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
-
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId('ethereum-portfolio');
- expect(portfolio).toBeVisible();
-
- // Verify token list exists with actual tokens
- const tokenList = canvas.queryByTestId('ethereum-portfolio-token-list');
- expect(tokenList).not.toBeNull();
-
- const tokenItems = tokenList?.querySelectorAll('[data-testid^="token-item-"]');
- expect(tokenItems?.length).toBeGreaterThan(0);
- },
- { timeout: 15000 },
- );
- },
-};
-
-export const RealDataBitcoin: Story = {
- name: 'Real Data: bitcoin',
- decorators: [withChainConfig],
- parameters: {
- chromatic: { delay: 5000 },
- docs: {
- description: {
- story: 'Fetches real balance and transactions from Bitcoin mainnet using mempool.space API.',
- },
- },
- },
- render: () => (
-
- ),
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
-
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId('bitcoin-portfolio');
- expect(portfolio).toBeVisible();
-
- // Verify token list exists with BTC balance
- const tokenList = canvas.queryByTestId('bitcoin-portfolio-token-list');
- expect(tokenList).not.toBeNull();
-
- const tokenItems = tokenList?.querySelectorAll('[data-testid^="token-item-"]');
- expect(tokenItems?.length).toBeGreaterThan(0);
- },
- { timeout: 15000 },
- );
- },
-};
-
-export const RealDataTron: Story = {
- name: 'Real Data: tron',
- decorators: [withChainConfig],
- parameters: {
- chromatic: { delay: 5000 },
- docs: {
- description: {
- story: 'Fetches real balance and transactions from Tron mainnet using TronGrid API.',
- },
- },
- },
- render: () => (
-
- ),
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
-
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId('tron-portfolio');
- expect(portfolio).toBeVisible();
-
- // Verify token list exists with TRX balance
- const tokenList = canvas.queryByTestId('tron-portfolio-token-list');
- expect(tokenList).not.toBeNull();
-
- const tokenItems = tokenList?.querySelectorAll('[data-testid^="token-item-"]');
- expect(tokenItems?.length).toBeGreaterThan(0);
- },
- { timeout: 15000 },
- );
- },
-};
-
-export const RealDataBinance: Story = {
- name: 'Real Data: binance',
- decorators: [withChainConfig],
- parameters: {
- chromatic: { delay: 5000 },
- docs: {
- description: {
- story: 'Fetches real BNB balance from BSC mainnet using public RPC.',
- },
- },
- },
- render: () => (
-
- ),
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
-
- await waitFor(
- () => {
- const portfolio = canvas.getByTestId('binance-portfolio');
- expect(portfolio).toBeVisible();
-
- // Verify token list exists with BNB balance
- const tokenList = canvas.queryByTestId('binance-portfolio-token-list');
- expect(tokenList).not.toBeNull();
-
- const tokenItems = tokenList?.querySelectorAll('[data-testid^="token-item-"]');
- expect(tokenItems?.length).toBeGreaterThan(0);
- },
- { timeout: 15000 },
- );
- },
-};
-
-export const CompareProvidersBfmeta: Story = {
- name: 'Compare Providers: BFMeta',
- decorators: [createCompareDecorator('bfmeta')],
- parameters: {
- chromatic: { delay: 8000 },
- docs: {
- description: {
- story: 'Dynamically compares all providers configured in default-chains.json for BFMeta chain.',
- },
- },
- },
- render: () => ,
-};
-
-export const CompareProvidersEthereum: Story = {
- name: 'Compare Providers: Ethereum',
- decorators: [createCompareDecorator('ethereum')],
- parameters: {
- chromatic: { delay: 8000 },
- docs: {
- description: {
- story: 'Dynamically compares all providers configured in default-chains.json for Ethereum chain.',
- },
- },
- },
- render: () => ,
-};
+// ==================== 各链正常数据 Stories ====================
-export const CompareProvidersTron: Story = {
- name: 'Compare Providers: Tron',
- decorators: [createCompareDecorator('tron')],
- parameters: {
- chromatic: { delay: 8000 },
- docs: {
- description: {
- story: 'Dynamically compares all providers configured in default-chains.json for Tron chain.',
- },
- },
+export const BFMetaNormalData: Story = {
+ name: 'BFMeta - Normal Data',
+ args: {
+ chainId: 'bfmeta',
+ chainName: 'BFMeta',
+ tokens: [
+ { symbol: 'BFT', name: 'BFT', balance: '1234.56789012', decimals: 8, chain: 'bfmeta' },
+ { symbol: 'USDT', name: 'USDT', balance: '500.00', decimals: 8, chain: 'bfmeta' },
+ ],
+ transactions: mockTransactions,
+ tokensSupported: true,
+ transactionsSupported: true,
},
- render: () => ,
};
-export const CompareProvidersBitcoin: Story = {
- name: 'Compare Providers: Bitcoin',
- decorators: [createCompareDecorator('bitcoin')],
- parameters: {
- chromatic: { delay: 8000 },
- docs: {
- description: {
- story: 'Dynamically compares all providers configured in default-chains.json for Bitcoin chain.',
- },
- },
+export const EthereumNormalData: Story = {
+ name: 'Ethereum - Normal Data',
+ args: {
+ chainId: 'ethereum',
+ chainName: 'Ethereum',
+ tokens: [
+ { symbol: 'ETH', name: 'Ethereum', balance: '23.683156206881918', decimals: 18, chain: 'ethereum' },
+ { symbol: 'USDC', name: 'USD Coin', balance: '1500.00', decimals: 6, chain: 'ethereum' },
+ ],
+ transactions: mockEthereumTransactions,
+ tokensSupported: true,
+ transactionsSupported: true,
},
- render: () => ,
};
-export const CompareProvidersBinance: Story = {
- name: 'Compare Providers: Binance',
- decorators: [createCompareDecorator('binance')],
- parameters: {
- chromatic: { delay: 8000 },
- docs: {
- description: {
- story: 'Dynamically compares all providers configured in default-chains.json for BNB Smart Chain.',
- },
- },
+export const BinanceNormalData: Story = {
+ name: 'Binance - Normal Data',
+ args: {
+ chainId: 'binance',
+ chainName: 'BNB Smart Chain',
+ tokens: [
+ { symbol: 'BNB', name: 'BNB', balance: '234.084063038409', decimals: 18, chain: 'binance' },
+ { symbol: 'BUSD', name: 'BUSD', balance: '2000.00', decimals: 18, chain: 'binance' },
+ ],
+ transactions: mockBinanceTransactions,
+ tokensSupported: true,
+ transactionsSupported: true,
},
- render: () => ,
};
-/**
- * ProviderFallbackWarning 截图验证 Stories
- *
- * 覆盖四种主要链的各种场景:
- * 1. 正常数据(supported: true)
- * 2. Fallback 警告(supported: false)
- */
-
-// ==================== BFMeta 链 ====================
-export const BFMetaNormalData: Story = {
- name: 'BFMeta - Normal Data',
+export const TronNormalData: Story = {
+ name: 'Tron - Normal Data',
args: {
- chainId: 'bfmeta',
- chainName: 'BFMeta',
+ chainId: 'tron',
+ chainName: 'Tron',
tokens: [
- { symbol: 'BFT', name: 'BFT', balance: '1234.56789012', decimals: 8, chain: 'bfmeta' },
- { symbol: 'USDT', name: 'USDT', balance: '500.00', decimals: 8, chain: 'bfmeta' },
+ { symbol: 'TRX', name: 'Tron', balance: '163377.648279', decimals: 6, chain: 'tron' },
+ { symbol: 'USDT', name: 'Tether', balance: '10000.00', decimals: 6, chain: 'tron' },
],
- transactions: mockTransactions,
+ transactions: mockTronTransactions,
tokensSupported: true,
transactionsSupported: true,
},
};
+// ==================== Fallback 警告 Stories ====================
+
export const BFMetaFallbackWarning: Story = {
name: 'BFMeta - Fallback Warning',
args: {
@@ -853,22 +233,6 @@ export const BFMetaFallbackWarning: Story = {
},
};
-// ==================== Ethereum 链 ====================
-export const EthereumNormalData: Story = {
- name: 'Ethereum - Normal Data (23.68 ETH)',
- args: {
- chainId: 'ethereum',
- chainName: 'Ethereum',
- tokens: [
- { symbol: 'ETH', name: 'Ethereum', balance: '23.683156206881918', decimals: 18, chain: 'ethereum' },
- { symbol: 'USDC', name: 'USD Coin', balance: '1500.00', decimals: 6, chain: 'ethereum' },
- ],
- transactions: mockEthereumTransactions,
- tokensSupported: true,
- transactionsSupported: true,
- },
-};
-
export const EthereumFallbackWarning: Story = {
name: 'Ethereum - Fallback Warning',
args: {
@@ -889,22 +253,6 @@ export const EthereumFallbackWarning: Story = {
},
};
-// ==================== BSC/Binance 链 ====================
-export const BinanceNormalData: Story = {
- name: 'Binance - Normal Data (234.08 BNB)',
- args: {
- chainId: 'binance',
- chainName: 'BNB Smart Chain',
- tokens: [
- { symbol: 'BNB', name: 'BNB', balance: '234.084063038409', decimals: 18, chain: 'binance' },
- { symbol: 'BUSD', name: 'BUSD', balance: '2000.00', decimals: 18, chain: 'binance' },
- ],
- transactions: mockBinanceTransactions,
- tokensSupported: true,
- transactionsSupported: true,
- },
-};
-
export const BinanceFallbackWarning: Story = {
name: 'Binance - Fallback Warning',
args: {
@@ -925,22 +273,6 @@ export const BinanceFallbackWarning: Story = {
},
};
-// ==================== Tron 链 ====================
-export const TronNormalData: Story = {
- name: 'Tron - Normal Data (163,377 TRX)',
- args: {
- chainId: 'tron',
- chainName: 'Tron',
- tokens: [
- { symbol: 'TRX', name: 'Tron', balance: '163377.648279', decimals: 6, chain: 'tron' },
- { symbol: 'USDT', name: 'Tether', balance: '10000.00', decimals: 6, chain: 'tron' },
- ],
- transactions: mockTronTransactions,
- tokensSupported: true,
- transactionsSupported: true,
- },
-};
-
export const TronFallbackWarning: Story = {
name: 'Tron - Fallback Warning',
args: {
@@ -961,7 +293,8 @@ export const TronFallbackWarning: Story = {
},
};
-// ==================== 混合场景 ====================
+// ==================== 边界情况 Stories ====================
+
export const PartialFallback: Story = {
name: 'Partial Fallback - Tokens OK, Transactions Failed',
args: {
@@ -971,8 +304,8 @@ export const PartialFallback: Story = {
{ symbol: 'ETH', name: 'Ethereum', balance: '5.5', decimals: 18, chain: 'ethereum' },
],
transactions: [],
- tokensSupported: true, // 余额查询成功
- transactionsSupported: false, // 交易历史查询失败
+ tokensSupported: true,
+ transactionsSupported: false,
transactionsFallbackReason: 'Etherscan API key invalid',
},
};
@@ -984,13 +317,12 @@ export const EmptyButSupported: Story = {
chainName: 'Ethereum',
tokens: [],
transactions: [],
- tokensSupported: true, // Provider 正常,只是没数据
+ tokensSupported: true,
transactionsSupported: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
- // 应该没有警告
expect(canvas.queryAllByTestId('provider-fallback-warning')).toHaveLength(0);
});
},
diff --git a/src/i18n/locales/en/transaction.json b/src/i18n/locales/en/transaction.json
index 8a476f426..226ddfd02 100644
--- a/src/i18n/locales/en/transaction.json
+++ b/src/i18n/locales/en/transaction.json
@@ -60,8 +60,7 @@
"twoStepSecretDescription": "This address has a security password set. Please enter your security password to confirm the transfer.",
"twoStepSecretPlaceholder": "Enter security password",
"twoStepSecretError": "Incorrect security password",
- "fee": "Fee",
- "previewTitle": "Transaction Preview"
+ "fee": "Fee"
},
"destroyPage": {
"title": "Destroy",
diff --git a/src/i18n/locales/zh-CN/transaction.json b/src/i18n/locales/zh-CN/transaction.json
index cb5017c59..b3c8b5a22 100644
--- a/src/i18n/locales/zh-CN/transaction.json
+++ b/src/i18n/locales/zh-CN/transaction.json
@@ -60,8 +60,7 @@
"twoStepSecretDescription": "该地址已设置安全密码,请输入安全密码确认转账。",
"twoStepSecretPlaceholder": "输入安全密码",
"twoStepSecretError": "安全密码错误",
- "fee": "手续费",
- "previewTitle": "交易预览"
+ "fee": "手续费"
},
"destroyPage": {
"title": "销毁",
diff --git a/src/pages/send/index.tsx b/src/pages/send/index.tsx
index bd2ec942e..af1aee480 100644
--- a/src/pages/send/index.tsx
+++ b/src/pages/send/index.tsx
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigation, useActivityParams, useFlow } from '@/stackflow';
-import { setTransferPreviewCallback, setTransferWalletLockCallback, setScannerResultCallback } from '@/stackflow/activities/sheets';
+import { setTransferConfirmCallback, setTransferWalletLockCallback, setScannerResultCallback } from '@/stackflow/activities/sheets';
import type { Contact, ContactAddress } from '@/stores';
import { addressBookStore, addressBookSelectors, preferencesActions } from '@/stores';
import { PageHeader } from '@/components/layout/page-header';
@@ -237,8 +237,8 @@ function SendPageContent() {
haptics.impact('light');
- // Set up callback: TransferPreview -> TransferWalletLock (合并的钱包锁+二次签名)
- setTransferPreviewCallback(
+ // Set up callback: TransferConfirm -> TransferWalletLock (合并的钱包锁+二次签名)
+ setTransferConfirmCallback(
async () => {
if (isWalletLockSheetOpen.current) return;
isWalletLockSheetOpen.current = true;
@@ -300,17 +300,13 @@ function SendPageContent() {
}
);
- push('TransferPreviewJob', {
+ push('TransferConfirmJob', {
amount: state.amount?.toFormatted() ?? '0',
symbol,
- decimals: String(state.asset?.decimals ?? chainConfig?.decimals ?? 8),
- fromAddress: currentChainAddress?.address ?? '',
toAddress: state.toAddress,
feeAmount: state.feeAmount?.toFormatted() ?? '0',
feeSymbol: state.feeSymbol,
feeLoading: state.feeLoading ? 'true' : 'false',
- chainId: selectedChain,
- chainName: selectedChainName,
});
};
diff --git a/src/stackflow/activities/sheets/TransferPreviewJob.tsx b/src/stackflow/activities/sheets/TransferPreviewJob.tsx
deleted file mode 100644
index 25a5e0dd2..000000000
--- a/src/stackflow/activities/sheets/TransferPreviewJob.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-/**
- * 转账预览确认组件
- *
- * 显示完整的交易详情预览,替代简单的 TransferConfirmJob
- * 流程: Send页面(填写) → TransferPreviewJob(交易详情预览) → TransferWalletLockJob(签名+广播+状态)
- */
-import { useState, useCallback, useRef, useEffect } from 'react';
-import type { ActivityComponentType } from '@stackflow/react';
-import { BottomSheet, SheetContent } from '@/components/layout/bottom-sheet';
-import { useTranslation } from 'react-i18next';
-import { cn } from '@/lib/utils';
-import { FeeDisplay } from '@/components/transaction/fee-display';
-import { AddressDisplay } from '@/components/wallet/address-display';
-import { ChainIcon } from '@/components/wallet/chain-icon';
-import { AmountDisplay } from '@/components/common';
-import type { ChainType } from '@/stores';
-import {
- IconArrowUp as ArrowUp,
- IconArrowDown as ArrowDown,
-} from '@tabler/icons-react';
-import { useFlow } from '../../stackflow';
-import { ActivityParamsProvider, useActivityParams } from '../../hooks';
-import { setFeeEditCallback } from './FeeEditJob';
-
-interface TransferPreviewConfig {
- onConfirm: () => Promise;
- minFee?: string | undefined;
- onFeeChange?: ((newFee: string) => void) | undefined;
-}
-
-let pendingConfig: TransferPreviewConfig | null = null;
-
-export function setTransferPreviewCallback(
- onConfirm: () => Promise,
- options?: { minFee?: string; onFeeChange?: (newFee: string) => void }
-) {
- pendingConfig = {
- onConfirm,
- minFee: options?.minFee,
- onFeeChange: options?.onFeeChange,
- };
-}
-
-function clearTransferPreviewCallback() {
- pendingConfig = null;
-}
-
-type TransferPreviewJobParams = {
- /** 转账金额 */
- amount: string;
- /** 资产符号 */
- symbol: string;
- /** 资产小数位 */
- decimals?: string;
- /** 法币价值 */
- fiatValue?: string;
- /** 发送地址 */
- fromAddress: string;
- /** 接收地址 */
- toAddress: string;
- /** 手续费金额 */
- feeAmount: string;
- /** 手续费符号 */
- feeSymbol: string;
- /** 手续费法币价值 */
- feeFiatValue?: string;
- /** 手续费加载中 */
- feeLoading?: string;
- /** 链ID */
- chainId: string;
- /** 链名称 */
- chainName?: string;
-};
-
-function TransferPreviewJobContent() {
- const { t } = useTranslation(['transaction', 'common']);
- const { pop, push } = useFlow();
- const params = useActivityParams();
-
- const [isConfirming, setIsConfirming] = useState(false);
- const [customFee, setCustomFee] = useState(null);
-
- // 捕获配置
- const configRef = useRef(pendingConfig);
- const initialized = useRef(false);
-
- if (!initialized.current && pendingConfig) {
- configRef.current = pendingConfig;
- clearTransferPreviewCallback();
- initialized.current = true;
- }
-
- useEffect(() => {
- return () => {
- clearTransferPreviewCallback();
- };
- }, []);
-
- const feeLoading = params.feeLoading === 'true';
- const displayFee = customFee ?? params.feeAmount;
- const canEditFee = !!configRef.current?.onFeeChange;
- const decimals = params.decimals ? parseInt(params.decimals, 10) : 8;
- const amountNum = parseFloat(params.amount) || 0;
-
- const handleEditFee = useCallback(() => {
- const config = configRef.current;
- if (!config?.onFeeChange) return;
-
- setFeeEditCallback(
- {
- currentFee: displayFee,
- minFee: config.minFee ?? params.feeAmount,
- symbol: params.feeSymbol,
- },
- (result) => {
- setCustomFee(result.fee);
- config.onFeeChange?.(result.fee);
- }
- );
- push('FeeEditJob', {});
- }, [displayFee, params.feeAmount, params.feeSymbol, push]);
-
- const handleConfirm = useCallback(async () => {
- const config = configRef.current;
-
- if (!config?.onConfirm || isConfirming) return;
-
- setIsConfirming(true);
- try {
- pop();
- await config.onConfirm();
- } finally {
- setIsConfirming(false);
- }
- }, [isConfirming, pop]);
-
- const handleClose = useCallback(() => {
- pop();
- }, [pop]);
-
- return (
-
-
-
- {/* 金额头部 */}
-
-
-
-
-
{t('type.send')}
-
- {params.fiatValue && (
-
≈ ${params.fiatValue}
- )}
-
-
-
- {/* 交易详情 */}
-
-
{t('detail.info')}
-
- {/* 发送地址 */}
-
-
{t('detail.fromAddress')}
-
-
-
-
-
- {/* 接收地址 */}
-
-
{t('detail.toAddress')}
-
-
-
-
-
- {/* 网络 */}
-
-
{t('detail.network')}
-
-
- {params.chainName ?? params.chainId}
-
-
-
-
-
- {/* 手续费 */}
-
- {t('detail.fee')}
-
-
-
-
- {/* 操作按钮 */}
-
-
-
-
-
-
-
- );
-}
-
-export const TransferPreviewJob: ActivityComponentType = ({ params }) => {
- return (
-
-
-
- );
-};
diff --git a/src/stackflow/activities/sheets/TransferWalletLockJob.tsx b/src/stackflow/activities/sheets/TransferWalletLockJob.tsx
index 4b5100b75..b313eaf17 100644
--- a/src/stackflow/activities/sheets/TransferWalletLockJob.tsx
+++ b/src/stackflow/activities/sheets/TransferWalletLockJob.tsx
@@ -55,7 +55,7 @@ type Step = 'wallet_lock' | 'two_step_secret';
function TransferWalletLockJobContent() {
const { t } = useTranslation(["security", "transaction", "common"]);
- const { pop, push, replace } = useFlow();
+ const { pop, push } = useFlow();
const { title } = useActivityParams();
const clipboard = useClipboard();
const toast = useToast();
@@ -110,12 +110,7 @@ function TransferWalletLockJobContent() {
return unsubscribe;
}, [txHash, selectedChain, currentWallet?.address]);
- // Navigate back to home (replace entire stack)
- const goToHome = useCallback(() => {
- replace('MainTabsActivity', {}, { animate: true });
- }, [replace]);
-
- // 上链成功后 5 秒倒计时自动关闭,返回首页
+ // 上链成功后 5 秒倒计时自动关闭
useEffect(() => {
if (txStatus !== 'confirmed') {
setCountdown(null);
@@ -127,7 +122,7 @@ function TransferWalletLockJobContent() {
setCountdown((prev) => {
if (prev === null || prev <= 1) {
clearInterval(timer);
- goToHome();
+ pop();
return null;
}
return prev - 1;
@@ -135,7 +130,7 @@ function TransferWalletLockJobContent() {
}, 1000);
return () => clearInterval(timer);
- }, [txStatus, goToHome]);
+ }, [txStatus, pop]);
// Get chain config for explorer URL
const chainConfig = useMemo(() => {
diff --git a/src/stackflow/activities/sheets/index.ts b/src/stackflow/activities/sheets/index.ts
index 4466a8ce9..b7040f658 100644
--- a/src/stackflow/activities/sheets/index.ts
+++ b/src/stackflow/activities/sheets/index.ts
@@ -11,7 +11,6 @@ export { WalletAddJob } from "./WalletAddJob";
export { WalletListJob } from "./WalletListJob";
export { SecurityWarningJob, setSecurityWarningConfirmCallback } from "./SecurityWarningJob";
export { TransferConfirmJob, setTransferConfirmCallback } from "./TransferConfirmJob";
-export { TransferPreviewJob, setTransferPreviewCallback } from "./TransferPreviewJob";
export { TransferWalletLockJob, setTransferWalletLockCallback } from "./TransferWalletLockJob";
export { FeeEditJob, setFeeEditCallback, type FeeEditConfig, type FeeEditResult } from "./FeeEditJob";
export { ScannerJob, setScannerResultCallback, scanValidators, getValidatorForChain, type ScannerJobParams, type ScannerResultEvent, type ScanValidator } from "./ScannerJob";
diff --git a/src/stackflow/stackflow.ts b/src/stackflow/stackflow.ts
index f81b1af91..9f41f8cd8 100644
--- a/src/stackflow/stackflow.ts
+++ b/src/stackflow/stackflow.ts
@@ -48,7 +48,6 @@ import {
WalletListJob,
SecurityWarningJob,
TransferConfirmJob,
- TransferPreviewJob,
TransferWalletLockJob,
FeeEditJob,
ScannerJob,
@@ -116,7 +115,6 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({
WalletListJob: '/job/wallet-list',
SecurityWarningJob: '/job/security-warning',
TransferConfirmJob: '/job/transfer-confirm',
- TransferPreviewJob: '/job/transfer-preview',
TransferWalletLockJob: '/job/transfer-wallet-lock',
FeeEditJob: '/job/fee-edit',
ScannerJob: '/job/scanner',
@@ -183,7 +181,6 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({
WalletListJob,
SecurityWarningJob,
TransferConfirmJob,
- TransferPreviewJob,
TransferWalletLockJob,
FeeEditJob,
ScannerJob,