diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9b7b487f1..9ff5b54a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -72,7 +72,8 @@ jobs:
# ==================== GitHub-hosted 标准链路 ====================
ci-standard:
- if: vars.USE_SELF_HOSTED != 'true'
+ if: always() && (vars.USE_SELF_HOSTED != 'true' || needs.ci-fast.result != 'success')
+ needs: [ci-fast]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
@@ -131,7 +132,7 @@ jobs:
fi
checks-standard:
- if: vars.USE_SELF_HOSTED != 'true'
+ if: always() && needs.ci-standard.result == 'success'
needs: ci-standard
runs-on: ubuntu-latest
steps:
diff --git a/miniapps/biobridge/vite.config.ts b/miniapps/biobridge/vite.config.ts
index 5fe2bc661..a7b051e96 100644
--- a/miniapps/biobridge/vite.config.ts
+++ b/miniapps/biobridge/vite.config.ts
@@ -8,6 +8,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs'
const E2E_SCREENSHOTS_DIR = resolve(__dirname, '../../e2e/__screenshots__/Desktop-Chrome/miniapp-ui.mock.spec.ts')
const MANIFEST_PATH = resolve(__dirname, 'manifest.json')
+const ENABLE_HTTPS_DEV = !process.env.CI
function getShortId(): string {
const manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf-8'))
@@ -51,7 +52,7 @@ function miniappPlugin(): Plugin {
export default defineConfig({
plugins: [
- mkcert(),
+ ...(ENABLE_HTTPS_DEV ? [mkcert()] : []),
react(),
tsconfigPaths(),
tailwindcss(),
@@ -63,7 +64,7 @@ export default defineConfig({
emptyOutDir: true,
},
server: {
- https: true,
+ https: ENABLE_HTTPS_DEV,
port: 5184,
fs: {
allow: [resolve(__dirname, '../..')],
diff --git a/miniapps/teleport/vite.config.ts b/miniapps/teleport/vite.config.ts
index 7d27a830b..5e1172bb4 100644
--- a/miniapps/teleport/vite.config.ts
+++ b/miniapps/teleport/vite.config.ts
@@ -8,6 +8,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs'
const E2E_SCREENSHOTS_DIR = resolve(__dirname, '../../e2e/__screenshots__/Desktop-Chrome/miniapp-ui.mock.spec.ts')
const MANIFEST_PATH = resolve(__dirname, 'manifest.json')
+const ENABLE_HTTPS_DEV = !process.env.CI
function getShortId(): string {
const manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf-8'))
@@ -51,7 +52,7 @@ function miniappPlugin(): Plugin {
export default defineConfig({
plugins: [
- mkcert(),
+ ...(ENABLE_HTTPS_DEV ? [mkcert()] : []),
react(),
tsconfigPaths(),
tailwindcss(),
@@ -63,7 +64,7 @@ export default defineConfig({
emptyOutDir: true,
},
server: {
- https: true,
+ https: ENABLE_HTTPS_DEV,
port: 5185,
fs: {
allow: [resolve(__dirname, '../..')],
diff --git a/src/components/security/pattern-lock.tsx b/src/components/security/pattern-lock.tsx
index ab4c6346e..f5697e478 100644
--- a/src/components/security/pattern-lock.tsx
+++ b/src/components/security/pattern-lock.tsx
@@ -19,6 +19,12 @@ export interface PatternLockProps {
success?: boolean;
/** 错误提示文案(可选,覆盖默认错误文案) */
errorText?: string;
+ /** 状态提示文案(可选,覆盖默认提示文案) */
+ hintText?: string;
+ /** 状态提示语气(仅在 hintText 生效时使用) */
+ hintTone?: 'default' | 'destructive' | 'primary';
+ /** 底部详情文案(可选) */
+ footerText?: string;
/** 额外的 className */
className?: string;
/** 网格大小 (默认 3x3) */
@@ -51,6 +57,9 @@ export function PatternLock({
error = false,
success = false,
errorText,
+ hintText,
+ hintTone = 'default',
+ footerText,
className,
size = 3,
'data-testid': testId,
@@ -504,6 +513,19 @@ export function PatternLock({
{errorText ?? t('patternLock.error')}
+ ) : hintText ? (
+
+ {hintText}
+
) : selectedNodes.length === 0 ? (
{t('patternLock.hint', { min: minPoints })}
@@ -525,7 +547,9 @@ export function PatternLock({
{/* 清除按钮 - 固定高度避免布局抖动 */}
- {selectedNodes.length > 0 && !disabled && !isErrorAnimating && (
+ {footerText ? (
+
{footerText}
+ ) : selectedNodes.length > 0 && !disabled && !isErrorAnimating ? (
- )}
+ ) : null}
);
diff --git a/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx b/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx
index 32e1462c7..291bd90f4 100644
--- a/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx
+++ b/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx
@@ -79,14 +79,26 @@ vi.mock('@/components/wallet/chain-address-display', () => ({
}));
vi.mock('@/components/security/pattern-lock', () => ({
- PatternLock: ({ onComplete }: { onComplete?: (nodes: number[]) => void }) => (
-
+ PatternLock: ({
+ onComplete,
+ hintText,
+ footerText,
+ }: {
+ onComplete?: (nodes: number[]) => void;
+ hintText?: string;
+ footerText?: string;
+ }) => (
+
+
+ {hintText ?? ''}
+ {footerText ?? ''}
+
),
patternToString: (nodes: number[]) => nodes.join(''),
}));
@@ -227,6 +239,39 @@ describe('miniapp confirm jobs regressions', () => {
expect(screen.getByTestId('miniapp-sheet-header')).toBeInTheDocument();
});
+ it('shows sign service feedback in pattern hint and detail footer', async () => {
+ vi.mocked(signUnsignedTransaction).mockRejectedValueOnce(new Error('sign service timeout'));
+
+ render(
+ ,
+ );
+
+ const signButton = screen.getByTestId('miniapp-sign-review-confirm');
+ await waitFor(() => {
+ expect(signButton).not.toBeDisabled();
+ });
+
+ fireEvent.click(signButton);
+ fireEvent.click(screen.getByTestId('pattern-lock'));
+
+ await waitFor(() => {
+ expect(screen.getByTestId('pattern-lock-hint').textContent?.length).toBeGreaterThan(0);
+ });
+ expect(screen.getByTestId('pattern-lock-footer').textContent).toContain('sign service timeout');
+ });
+
it('does not pass raw amount directly to display layer', () => {
render(
{
fireEvent.click(confirmButton);
fireEvent.click(screen.getByTestId('pattern-lock'));
- expect(await screen.findByTestId('miniapp-transfer-error')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByTestId('pattern-lock-hint').textContent?.length).toBeGreaterThan(0);
+ });
+ expect(screen.getByTestId('pattern-lock-footer').textContent).toContain('Request timeout');
await waitFor(() => {
expect(screen.queryByTestId('miniapp-transfer-broadcasting-status')).not.toBeInTheDocument();
});
diff --git a/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx b/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx
index 13bf8270a..8e09f933f 100644
--- a/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx
+++ b/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx
@@ -44,6 +44,32 @@ type MiniappSignTransactionJobParams = {
type SignStep = MiniappSignFlowStep;
+function collectErrorMessages(error: unknown): string[] {
+ const messages: string[] = [];
+ const visited = new Set();
+ let current: unknown = error;
+
+ while (current instanceof Error && !visited.has(current)) {
+ visited.add(current);
+ if (current.message) {
+ messages.push(current.message);
+ }
+ current = (current as Error & { cause?: unknown }).cause;
+ }
+
+ return messages;
+}
+
+function extractSignErrorDetail(error: unknown): string | null {
+ for (const message of collectErrorMessages(error)) {
+ const normalized = message.trim();
+ if (normalized.length > 0) {
+ return normalized;
+ }
+ }
+ return null;
+}
+
function MiniappSignTransactionJobContent() {
const { t } = useTranslation('common');
const { pop } = useFlow();
@@ -56,6 +82,7 @@ function MiniappSignTransactionJobContent() {
const [patternError, setPatternError] = useState(false);
const [twoStepSecretError, setTwoStepSecretError] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
+ const [errorDetail, setErrorDetail] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [requiresTwoStepSecret, setRequiresTwoStepSecret] = useState(false);
const [isResolvingTwoStepSecret, setIsResolvingTwoStepSecret] = useState(true);
@@ -85,6 +112,7 @@ function MiniappSignTransactionJobContent() {
setTwoStepSecret('');
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}, []);
useEffect(() => {
@@ -120,25 +148,27 @@ function MiniappSignTransactionJobContent() {
const handlePatternChange = useCallback(
(nextPattern: number[]) => {
- if (patternError || errorMessage || twoStepSecretError) {
+ if (patternError || errorMessage || twoStepSecretError || errorDetail) {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}
setPattern(nextPattern);
},
- [patternError, errorMessage, twoStepSecretError],
+ [patternError, errorMessage, twoStepSecretError, errorDetail],
);
const handleTwoStepSecretChange = useCallback(
(value: string) => {
- if (twoStepSecretError || errorMessage) {
+ if (twoStepSecretError || errorMessage || errorDetail) {
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}
setTwoStepSecret(value);
},
- [twoStepSecretError, errorMessage],
+ [twoStepSecretError, errorMessage, errorDetail],
);
const performSign = useCallback(
@@ -180,6 +210,7 @@ function MiniappSignTransactionJobContent() {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
try {
setStep('signing');
@@ -189,15 +220,20 @@ function MiniappSignTransactionJobContent() {
if (isMiniappWalletLockError(error)) {
setPatternError(true);
setErrorMessage(t('walletLock.error'));
+ setErrorDetail(null);
+ setStep('wallet_lock');
} else if (isMiniappTwoStepSecretError(error)) {
setPatternError(false);
setTwoStepSecretError(true);
setErrorMessage(t('transaction:sendPage.twoStepSecretError'));
+ setErrorDetail(null);
setStep('two_step_secret');
} else {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(t('signingFailed'));
+ setErrorDetail(extractSignErrorDetail(error));
+ setStep('wallet_lock');
}
setPattern([]);
} finally {
@@ -216,6 +252,7 @@ function MiniappSignTransactionJobContent() {
setIsSubmitting(true);
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
try {
setStep('signing');
@@ -225,14 +262,17 @@ function MiniappSignTransactionJobContent() {
if (isMiniappTwoStepSecretError(error)) {
setTwoStepSecretError(true);
setErrorMessage(t('transaction:sendPage.twoStepSecretError'));
+ setErrorDetail(null);
setStep('two_step_secret');
} else if (isMiniappWalletLockError(error)) {
setPatternError(true);
setErrorMessage(t('walletLock.error'));
+ setErrorDetail(null);
setStep('wallet_lock');
} else {
setTwoStepSecretError(false);
setErrorMessage(t('signingFailed'));
+ setErrorDetail(extractSignErrorDetail(error));
setStep('two_step_secret');
}
} finally {
@@ -261,6 +301,9 @@ function MiniappSignTransactionJobContent() {
}
}, [unsignedTx]);
+ const walletLockServiceMessage = !patternError && errorMessage ? errorMessage : null;
+ const walletLockErrorDetail = !patternError ? errorDetail : null;
+
return (
@@ -343,6 +386,7 @@ function MiniappSignTransactionJobContent() {
{t('cancel')}
- {errorMessage && !patternError && (
-
{errorMessage}
- )}
-
{isSubmitting && (
diff --git a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx
index d43599b9c..14a73d581 100644
--- a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx
+++ b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx
@@ -26,7 +26,7 @@ import { useToast } from '@/services';
import { superjson } from '@biochain/chain-effect';
import { Amount } from '@/types/amount';
import { findMiniappWalletIdByAddress, resolveMiniappChainId } from './miniapp-wallet';
-import { createMiniappUnsupportedPipelineError, mapMiniappTransferErrorToMessage } from './miniapp-transfer-error';
+import { createMiniappUnsupportedPipelineError, resolveMiniappTransferErrorFeedback } from './miniapp-transfer-error';
import { parseMiniappTransferAmountRaw } from './miniapp-transfer-amount';
import type { SignedTransaction, UnsignedTransaction } from '@/services/ecosystem';
import {
@@ -163,6 +163,7 @@ function MiniappTransferConfirmJobContent() {
const [patternError, setPatternError] = useState(false);
const [twoStepSecretError, setTwoStepSecretError] = useState(false);
const [errorMessage, setErrorMessage] = useState
(null);
+ const [errorDetail, setErrorDetail] = useState(null);
const [phase, setPhase] = useState('idle');
const [successCountdown, setSuccessCountdown] = useState(null);
const [requiresTwoStepSecret, setRequiresTwoStepSecret] = useState(false);
@@ -275,6 +276,7 @@ function MiniappTransferConfirmJobContent() {
setTwoStepSecret('');
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}, []);
useEffect(() => {
@@ -533,25 +535,27 @@ function MiniappTransferConfirmJobContent() {
const handlePatternChange = useCallback(
(nextPattern: number[]) => {
- if (patternError || errorMessage || twoStepSecretError) {
+ if (patternError || errorMessage || twoStepSecretError || errorDetail) {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}
setPattern(nextPattern);
},
- [patternError, errorMessage, twoStepSecretError],
+ [patternError, errorMessage, twoStepSecretError, errorDetail],
);
const handleTwoStepSecretChange = useCallback(
(value: string) => {
- if (twoStepSecretError || errorMessage) {
+ if (twoStepSecretError || errorMessage || errorDetail) {
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
}
setTwoStepSecret(value);
},
- [twoStepSecretError, errorMessage],
+ [twoStepSecretError, errorMessage, errorDetail],
);
const performTransfer = useCallback(
@@ -644,7 +648,7 @@ function MiniappTransferConfirmJobContent() {
const handleTransferFailure = useCallback(
(error: unknown, inputStep: TransferInputStep) => {
- const mappedError = mapMiniappTransferErrorToMessage(t, error, resolvedChainId);
+ const { message: mappedError, detail: mappedErrorDetail } = resolveMiniappTransferErrorFeedback(t, error, resolvedChainId);
if (isBackgroundBroadcastRef.current) {
if (isMountedRef.current) {
setStep(inputStep);
@@ -674,6 +678,7 @@ function MiniappTransferConfirmJobContent() {
setPatternError(true);
setTwoStepSecretError(false);
setErrorMessage(t('walletLock.error'));
+ setErrorDetail(null);
setStep('wallet_lock');
setPattern([]);
return;
@@ -683,6 +688,7 @@ function MiniappTransferConfirmJobContent() {
setPatternError(false);
setTwoStepSecretError(true);
setErrorMessage(t('transaction:sendPage.twoStepSecretError'));
+ setErrorDetail(null);
setStep('two_step_secret');
return;
}
@@ -690,6 +696,7 @@ function MiniappTransferConfirmJobContent() {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(mappedError);
+ setErrorDetail(mappedErrorDetail);
setStep(inputStep);
if (inputStep === 'wallet_lock') {
@@ -716,6 +723,7 @@ function MiniappTransferConfirmJobContent() {
setPatternError(false);
setTwoStepSecretError(false);
setErrorMessage(null);
+ setErrorDetail(null);
try {
const result = await performTransfer(password, paySecret);
@@ -844,6 +852,9 @@ function MiniappTransferConfirmJobContent() {
pop();
}, [emitSheetClosed, emitTransferResult, handleSuccessClose, isBroadcasting, isBuilding, isSuccess, logTransferSheet, moveToBackgroundBroadcast, pop]);
+ const walletLockServiceMessage = !patternError && errorMessage ? errorMessage : null;
+ const walletLockErrorDetail = !patternError ? errorDetail : null;
+
return (
@@ -1019,12 +1030,15 @@ function MiniappTransferConfirmJobContent() {
disabled={isBusy || !walletId}
error={patternError}
errorText={patternError ? t('walletLock.error') : undefined}
+ hintText={walletLockServiceMessage ?? undefined}
+ hintTone={walletLockServiceMessage ? 'destructive' : 'default'}
+ footerText={
+ walletLockErrorDetail
+ ? `${t('common:service.technicalDetails')}: ${walletLockErrorDetail}`
+ : undefined
+ }
/>
- {errorMessage && !patternError && (
-
{errorMessage}
- )}
-
{isBuilding && (
{
const error = new Error('some-random-error');
expect(mapMiniappTransferErrorToMessage(t, error, chainId)).toBe('some-random-error');
});
+
+ it('extracts technical detail from broadcast timeout cause', () => {
+ const error = new ChainServiceError(
+ ChainErrorCodes.TX_BROADCAST_FAILED,
+ 'Failed to broadcast transaction',
+ undefined,
+ new Error('Request timeout'),
+ );
+ expect(resolveMiniappTransferErrorFeedback(t, error, chainId)).toEqual({
+ message: 'transaction:broadcast.timeout',
+ detail: 'Request timeout',
+ });
+ });
+
+ it('falls back to raw message when no parsed detail exists', () => {
+ const error = new Error('opaque-upstream-error');
+ expect(resolveMiniappTransferErrorFeedback(t, error, chainId)).toEqual({
+ message: 'opaque-upstream-error',
+ detail: 'opaque-upstream-error',
+ });
+ });
});
diff --git a/src/stackflow/activities/sheets/miniapp-transfer-error.ts b/src/stackflow/activities/sheets/miniapp-transfer-error.ts
index 69ceaa659..dee5d1597 100644
--- a/src/stackflow/activities/sheets/miniapp-transfer-error.ts
+++ b/src/stackflow/activities/sheets/miniapp-transfer-error.ts
@@ -3,6 +3,11 @@ import { ChainErrorCodes, ChainServiceError } from '@/services/chain-adapter/typ
const MINIAPP_TRANSFER_UNSUPPORTED_PIPELINE = 'MINIAPP_TRANSFER_UNSUPPORTED_PIPELINE';
+export type MiniappTransferErrorFeedback = {
+ message: string;
+ detail: string | null;
+};
+
function isTimeoutMessage(message: string): boolean {
return /timeout|timed out|etimedout|aborterror|aborted/i.test(message);
}
@@ -71,6 +76,37 @@ function withBroadcastReason(baseMessage: string, reason: string | null): string
return `${baseMessage}: ${reason}`;
}
+function extractRawErrorDetail(error: unknown): string | null {
+ const [firstMessage] = collectErrorMessages(error);
+ if (!firstMessage) {
+ return null;
+ }
+ const trimmed = firstMessage.trim();
+ if (!trimmed) {
+ return null;
+ }
+ return trimmed;
+}
+
+function extractParsedErrorDetail(error: unknown): string | null {
+ const broadcastReason = extractBroadcastFailureReason(error);
+ if (broadcastReason) {
+ return broadcastReason;
+ }
+
+ const messages = collectErrorMessages(error)
+ .map((message) => message.trim())
+ .filter((message) => message.length > 0);
+ if (messages.length === 0) {
+ return null;
+ }
+
+ const [firstMeaningfulMessage] = messages.filter(
+ (message) => !isGenericBroadcastFailureMessage(message) && !isBroadcastFailedMessage(message),
+ );
+ return firstMeaningfulMessage ?? messages[0] ?? null;
+}
+
export function createMiniappUnsupportedPipelineError(chainId: string): ChainServiceError {
return new ChainServiceError(ChainErrorCodes.NOT_SUPPORTED, MINIAPP_TRANSFER_UNSUPPORTED_PIPELINE, {
scope: 'miniapp-transfer',
@@ -78,66 +114,131 @@ export function createMiniappUnsupportedPipelineError(chainId: string): ChainSer
});
}
-export function mapMiniappTransferErrorToMessage(t: TFunction, error: unknown, chainId: string): string {
+export function resolveMiniappTransferErrorFeedback(
+ t: TFunction,
+ error: unknown,
+ chainId: string,
+): MiniappTransferErrorFeedback {
+ let message: string | null = null;
if (error instanceof ChainServiceError) {
if (error.code === ChainErrorCodes.NOT_SUPPORTED && error.message === MINIAPP_TRANSFER_UNSUPPORTED_PIPELINE) {
- return t('common:miniappTransferUnsupportedPipeline', { chainId });
+ message = t('common:miniappTransferUnsupportedPipeline', { chainId });
+ return {
+ message,
+ detail: extractRawErrorDetail(error),
+ };
}
if (error.code === ChainErrorCodes.TRANSACTION_TIMEOUT) {
- return t('transaction:broadcast.timeout');
+ message = t('transaction:broadcast.timeout');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (error.code === ChainErrorCodes.TX_BROADCAST_FAILED) {
if (hasTimeoutMessageInError(error)) {
- return t('transaction:broadcast.timeout');
+ message = t('transaction:broadcast.timeout');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
- return withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ message = withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (error.code === ChainErrorCodes.TX_BUILD_FAILED) {
const reason = typeof error.details?.reason === 'string' ? error.details.reason.trim() : '';
const displayMessage = reason || error.message;
if (isSelfTransferMessage(displayMessage)) {
- return t('error:validation.cannotTransferToSelf');
+ message = t('error:validation.cannotTransferToSelf');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (displayMessage) {
- return displayMessage;
+ message = displayMessage;
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
}
if (error.code === ChainErrorCodes.NETWORK_ERROR) {
if (hasTimeoutMessageInError(error)) {
- return t('transaction:broadcast.timeout');
+ message = t('transaction:broadcast.timeout');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (hasBroadcastFailedMessageInError(error)) {
- return withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ message = withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
}
}
if (error instanceof Error) {
if (error.message === MINIAPP_TRANSFER_UNSUPPORTED_PIPELINE) {
- return t('common:miniappTransferUnsupportedPipeline', { chainId });
+ message = t('common:miniappTransferUnsupportedPipeline', { chainId });
+ return {
+ message,
+ detail: extractRawErrorDetail(error),
+ };
}
if (hasTimeoutMessageInError(error)) {
- return t('transaction:broadcast.timeout');
+ message = t('transaction:broadcast.timeout');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (hasBroadcastFailedMessageInError(error)) {
- return withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ message = withBroadcastReason(t('transaction:broadcast.failed'), extractBroadcastFailureReason(error));
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
if (isSelfTransferMessage(error.message)) {
- return t('error:validation.cannotTransferToSelf');
+ message = t('error:validation.cannotTransferToSelf');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
const [firstMessage] = collectErrorMessages(error);
if (firstMessage) {
- return firstMessage;
+ message = firstMessage;
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
}
}
- return t('transaction:broadcast.unknown');
+ message = t('transaction:broadcast.unknown');
+ return {
+ message,
+ detail: extractParsedErrorDetail(error) ?? extractRawErrorDetail(error),
+ };
+}
+
+export function mapMiniappTransferErrorToMessage(t: TFunction, error: unknown, chainId: string): string {
+ return resolveMiniappTransferErrorFeedback(t, error, chainId).message;
}