diff --git a/src/hooks/useContractFunctionHook.test.tsx b/src/hooks/useContractFunctionHook.test.tsx
new file mode 100644
index 0000000..6b8a31a
--- /dev/null
+++ b/src/hooks/useContractFunctionHook.test.tsx
@@ -0,0 +1,367 @@
+import { renderHook, act } from '@testing-library/react'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { useContractFunctionHook } from './useContractFunctionHook'
+
+vi.mock('../components/common/contexts/DocumentContext', () => ({
+ useDocumentContext: () => ({ keyId: 'test-key' }),
+}))
+
+const { mockTransferHolder } = vi.hoisted(() => ({
+ mockTransferHolder: vi.fn(),
+}))
+
+vi.mock('@trustvc/trustvc', () => ({
+ transferHolder: mockTransferHolder,
+ transferBeneficiary: vi.fn(),
+ transferOwners: vi.fn(),
+ rejectTransferHolder: vi.fn(),
+ rejectTransferBeneficiary: vi.fn(),
+ rejectTransferOwners: vi.fn(),
+ nominate: vi.fn(),
+ returnToIssuer: vi.fn(),
+ rejectReturned: vi.fn(),
+ acceptReturned: vi.fn(),
+}))
+
+const mockContract = { address: '0xabc' } as any
+
+describe('useContractFunctionHook', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ describe('send — missing contract or method', () => {
+ it('sets ERROR and errorMessage when contract is undefined', async () => {
+ const { result } = renderHook(() =>
+ useContractFunctionHook(undefined, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe(
+ 'Contract or method is not specified'
+ )
+ })
+
+ it('sets ERROR and errorMessage when method is undefined', async () => {
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, undefined)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe(
+ 'Contract or method is not specified'
+ )
+ })
+ })
+
+ describe('send — MetaMask numeric error codes', () => {
+ it('maps code 4001 to "User Rejected Transaction" and sets ERROR state', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 4001 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe('User Rejected Transaction')
+ })
+
+ it('user rejection now sets ERROR state (not UNINITIALIZED)', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 4001 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ })
+
+ it('maps code 4100 to "Unauthorized: Account or method not authorized"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 4100 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe(
+ 'Unauthorized: Account or method not authorized'
+ )
+ })
+
+ it('maps code 4900 to "Wallet Disconnected"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 4900 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Wallet Disconnected')
+ })
+
+ it('maps code -32603 to "Internal Error"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: -32603 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Internal Error')
+ })
+
+ it('maps code -32000 to "Invalid Input"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: -32000 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Invalid Input')
+ })
+
+ it('maps code -32003 to "Transaction Rejected"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: -32003 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Transaction Rejected')
+ })
+ })
+
+ describe('send — ethers string error codes', () => {
+ it('maps ACTION_REJECTED to "User Rejected Transaction"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 'ACTION_REJECTED' })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe('User Rejected Transaction')
+ })
+
+ it('maps INSUFFICIENT_FUNDS to "Insufficient Funds"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 'INSUFFICIENT_FUNDS' })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Insufficient Funds')
+ })
+
+ it('maps UNPREDICTABLE_GAS_LIMIT to "Unable to Estimate Gas"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({
+ code: 'UNPREDICTABLE_GAS_LIMIT',
+ })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Unable to Estimate Gas')
+ })
+
+ it('maps CALL_EXCEPTION to "Contract Call Failed"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 'CALL_EXCEPTION' })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Contract Call Failed')
+ })
+
+ it('maps NETWORK_ERROR to "Network Error"', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 'NETWORK_ERROR' })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('Network Error')
+ })
+ })
+
+ describe('send — fallback error handling', () => {
+ it('uses error.message when code is not in any map', async () => {
+ mockTransferHolder.mockRejectedValueOnce(
+ new Error('Transaction failed: nonce too low')
+ )
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe(
+ 'Transaction failed: nonce too low'
+ )
+ })
+
+ it('returns empty string for errors with unknown numeric code and no message', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 9999 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('')
+ })
+
+ it('returns empty string for errors with unknown string code and no message', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 'UNKNOWN_CODE' })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('')
+ })
+ })
+
+ describe('send — error thrown inside transaction.wait()', () => {
+ it('applies error mapping to errors thrown during wait()', async () => {
+ const mockWait = vi.fn().mockRejectedValueOnce({ code: 4001 })
+ mockTransferHolder.mockResolvedValueOnce({ wait: mockWait })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe('User Rejected Transaction')
+ })
+ })
+
+ describe('reset', () => {
+ it('clears errorMessage and returns to UNINITIALIZED', async () => {
+ mockTransferHolder.mockRejectedValueOnce({ code: 4001 })
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContract, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.send({})
+ })
+
+ expect(result.current.errorMessage).toBe('User Rejected Transaction')
+
+ act(() => {
+ result.current.reset()
+ })
+
+ expect(result.current.errorMessage).toBeUndefined()
+ expect(result.current.state).toBe('UNINITIALIZED')
+ })
+ })
+
+ describe('call — error handling', () => {
+ it('sets ERROR state without errorMessage when contract is missing', async () => {
+ const { result } = renderHook(() =>
+ useContractFunctionHook(undefined, 'transferHolder' as any)
+ )
+
+ await act(async () => {
+ await result.current.call()
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBeUndefined()
+ })
+
+ it('maps call error code to errorMessage', async () => {
+ const mockContractWithMethod = {
+ functions: {
+ someMethod: vi
+ .fn()
+ .mockRejectedValueOnce({ code: 'INSUFFICIENT_FUNDS' }),
+ },
+ } as any
+
+ const { result } = renderHook(() =>
+ useContractFunctionHook(mockContractWithMethod, 'someMethod' as any)
+ )
+
+ await act(async () => {
+ await result.current.call()
+ })
+
+ expect(result.current.state).toBe('ERROR')
+ expect(result.current.errorMessage).toBe('Insufficient Funds')
+ })
+ })
+})
diff --git a/src/hooks/useContractFunctionHook.tsx b/src/hooks/useContractFunctionHook.tsx
index cac9701..9a4ef69 100644
--- a/src/hooks/useContractFunctionHook.tsx
+++ b/src/hooks/useContractFunctionHook.tsx
@@ -16,6 +16,49 @@ import { TitleEscrow, TradeTrustToken } from '../types'
import { TypedContractMethod } from '@trustvc/trustvc'
import { useDocumentContext } from '../components/common/contexts/DocumentContext'
+const METAMASK_NUMERIC_CODES: Record
= {
+ 4001: 'User Rejected Transaction',
+ 4100: 'Unauthorized: Account or method not authorized',
+ 4200: 'Unsupported Method',
+ 4900: 'Wallet Disconnected',
+ 4901: 'Chain Disconnected',
+ [-32700]: 'Parse Error',
+ [-32600]: 'Invalid Request',
+ [-32601]: 'Method Not Found',
+ [-32602]: 'Invalid Parameters',
+ [-32603]: 'Internal Error',
+ [-32000]: 'Invalid Input',
+ [-32001]: 'Resource Not Found',
+ [-32002]: 'Request Already Pending',
+ [-32003]: 'Transaction Rejected',
+ [-32004]: 'Method Not Supported',
+ [-32005]: 'Request Limit Exceeded',
+}
+
+const ETHERS_STRING_CODES: Record = {
+ ACTION_REJECTED: 'User Rejected Transaction',
+ INSUFFICIENT_FUNDS: 'Insufficient Funds',
+ UNPREDICTABLE_GAS_LIMIT: 'Unable to Estimate Gas',
+ NETWORK_ERROR: 'Network Error',
+ SERVER_ERROR: 'Server Error',
+ TIMEOUT: 'Request Timed Out',
+ CALL_EXCEPTION: 'Contract Call Failed',
+ TRANSACTION_REPLACED: 'Transaction Replaced',
+ NONCE_EXPIRED: 'Nonce Already Used',
+ REPLACEMENT_UNDERPRICED: 'Replacement Transaction Underpriced',
+}
+
+const getMetaMaskErrorMessage = (e: unknown): string => {
+ const code = (e as any)?.code
+ if (typeof code === 'number' && code in METAMASK_NUMERIC_CODES) {
+ return METAMASK_NUMERIC_CODES[code]
+ }
+ if (typeof code === 'string' && code in ETHERS_STRING_CODES) {
+ return ETHERS_STRING_CODES[code]
+ }
+ return (e as Error)?.message || ''
+}
+
// Create a mapping of method names to trustvc functions
const trustvcFunctions: Record any> = {
transferHolder,
@@ -68,18 +111,17 @@ export function useContractFunctionHook<
state: ContractFunctionState
receipt?: ContractReceipt
transaction?: ContractTransaction
- error?: Error
+ errorMessage?: string
value?: UnwrapPromise<
ReturnType any ? T[S] : never>
>
events?: ContractReceipt['events']
transactionHash?: string
- errorMessage?: string
} {
const [state, setState] = useState('UNINITIALIZED')
const [receipt, setReceipt] = useState()
const [transaction, setTransaction] = useState()
- const [error, setError] = useState()
+ const [errorMessage, setErrorMessage] = useState()
const [value, setValue] =
useState<
UnwrapPromise<
@@ -93,7 +135,7 @@ export function useContractFunctionHook<
setState('UNINITIALIZED')
setReceipt(undefined)
setTransaction(undefined)
- setError(undefined)
+ setErrorMessage(undefined)
setValue(undefined)
}
@@ -101,7 +143,7 @@ export function useContractFunctionHook<
const sendFn = (async (params: any) => {
if (!contract || !method) {
setState('ERROR')
- setError(new Error('Contract or method is not specified'))
+ setErrorMessage('Contract or method is not specified')
return
}
resetState()
@@ -135,11 +177,7 @@ export function useContractFunctionHook<
setState('CONFIRMED')
setReceipt(_receipt)
} catch (e) {
- if ((e as Error).message?.includes('user rejected transaction')) {
- setState('UNINITIALIZED')
- return
- }
- setError(e as Error)
+ setErrorMessage(getMetaMaskErrorMessage(e))
setState('ERROR')
}
}) as TypedContractMethod<
@@ -151,7 +189,6 @@ export function useContractFunctionHook<
const callFn = (async (...params: any[]) => {
if (!contract || !method) {
setState('ERROR')
- setError(new Error('Contract or method is not specified'))
return
}
resetState()
@@ -167,7 +204,7 @@ export function useContractFunctionHook<
setState('CONFIRMED')
setValue(response)
} catch (e) {
- setError(e as Error)
+ setErrorMessage(getMetaMaskErrorMessage(e))
setState('ERROR')
}
}) as TypedContractMethod<
@@ -178,7 +215,6 @@ export function useContractFunctionHook<
const transactionHash = transaction?.hash
const events = receipt?.events
- const errorMessage = error?.message
// eslint-disable-next-line react-hooks/exhaustive-deps
const send = useCallback(sendFn, [
@@ -202,7 +238,6 @@ export function useContractFunctionHook<
transaction,
transactionHash,
errorMessage,
- error,
value,
reset,
}
From 98a145d7a3bb3d7c12496f7b3d54a21149dd9ebf Mon Sep 17 00:00:00 2001
From: RishabhS7 <59636880+RishabhS7@users.noreply.github.com>
Date: Wed, 29 Apr 2026 15:41:11 +0530
Subject: [PATCH 4/4] fix: merge (#54)
* fix: merge
* fix: update test mocks and add obfuscation notice for documents
* fix: stub AssetManagementApplication
* fix: expired tag
* fix: unknown error metamask
---------
---
.../AssetManagementApplication.test.tsx | 12 ++---
.../AssetManagementApplication/index.tsx | 17 +++----
.../AssetManagementForm.tsx | 1 -
.../ActionSelectionForm.tsx | 10 ----
.../home/VerifySection/ObfuscatedMessage.tsx | 49 +++++++++++++++++++
.../home/VerifySection/VerifyResult.test.tsx | 7 +++
.../home/VerifySection/VerifyResult.tsx | 16 +++++-
.../home/VerifySection/VerifySection.test.tsx | 1 +
.../home/VerifySection/VerifySection.tsx | 2 +
.../home/VerifySection/useVerify.ts | 7 ++-
src/hooks/useContractFunctionHook.test.tsx | 20 +-------
src/hooks/useContractFunctionHook.tsx | 3 +-
src/utils/helper.ts | 6 +++
13 files changed, 102 insertions(+), 49 deletions(-)
create mode 100644 src/components/home/VerifySection/ObfuscatedMessage.tsx
diff --git a/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx b/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx
index f69d2b5..9974126 100644
--- a/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx
+++ b/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx
@@ -68,10 +68,10 @@ vi.mock('../AssetManagementForm', () => ({
),
}))
-// Mock TagBordered
+// Mock Tag
vi.mock('../../common/Tag', () => ({
- TagBordered: ({ children, ...props }: any) => (
-
+ Tag: ({ children, ...props }: any) => (
+
{children}
),
@@ -166,14 +166,14 @@ describe('AssetManagementApplication', () => {
/>
)
- expect(screen.getByTestId('expiredDoc')).toBeInTheDocument()
+ expect(screen.getByTestId('tag')).toBeInTheDocument()
expect(screen.getByText('Expired')).toBeInTheDocument()
})
it('does not render expired tag when document is not expired', () => {
render(
)
- expect(screen.queryByTestId('expiredDoc')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('tag')).not.toBeInTheDocument()
})
})
@@ -413,7 +413,7 @@ describe('AssetManagementApplication', () => {
expect(
screen.queryByTestId('asset-management-form')
).not.toBeInTheDocument()
- expect(screen.queryByTestId('expiredDoc')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('tag')).not.toBeInTheDocument()
})
})
diff --git a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx
index 5515a75..f5a6e33 100644
--- a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx
+++ b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx
@@ -11,7 +11,7 @@ import { useTokenRegistryContract } from '../../../hooks/useTokenRegistryContrac
import { useTokenRegistryRole } from '../../../hooks/useTokenRegistryRole'
import { AssetManagementActions } from '../AssetManagementActions'
import { AssetManagementForm } from '../AssetManagementForm'
-import { TagBordered } from '../../common/Tag'
+import { Tag } from '../../common/Tag'
import { useTokenRegistryVersion } from '../../../hooks/useTokenRegistryVersion'
import { TokenRegistryVersions } from '../../../constants'
@@ -23,7 +23,7 @@ interface AssetManagementIsTransferableDocumentProps {
setShowEndorsementChain: (payload: boolean) => void
refreshEndorsementChain?: () => void
isTransferableDocument: true
- isExpired: boolean
+ isExpired?: boolean
}
interface AssetManagementIsNotTransferableDocumentProps {
@@ -200,16 +200,13 @@ export const AssetManagementApplication: FunctionComponent<
/>
) : (
isExpired && (
-
-
+
-
- Expired
-
-
+
Expired
+
)
)}
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx
index df11d81..1479c59 100644
--- a/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx
+++ b/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx
@@ -241,7 +241,6 @@ export const AssetManagementForm: FunctionComponent<
isTokenBurnt={isTokenBurnt}
setShowEndorsementChain={setShowEndorsementChain}
isTitleEscrow={isTitleEscrow}
- isExpired={isExpired}
/>
)}
{(formAction === AssetManagementActions.TransferHolder ||
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
index baa4e58..b3fa1f8 100644
--- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
+++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
@@ -24,7 +24,6 @@ interface ActionSelectionFormProps {
isTitleEscrow: boolean
isRejectPendingConfirmation?: boolean
isTokenBurnt: boolean
- isExpired?: boolean
canReturnToIssuer: boolean
canHandleShred?: boolean
@@ -50,7 +49,6 @@ export const ActionSelectionForm: FunctionComponent<
isTokenBurnt,
isTitleEscrow,
isRejectPendingConfirmation,
- isExpired,
canTransferHolder,
canTransferBeneficiary,
canTransferOwners,
@@ -187,14 +185,6 @@ export const ActionSelectionForm: FunctionComponent<
)}
- {isExpired && (
-
- ETR Expired
-
- )}
{!isTokenBurnt && (
diff --git a/src/components/home/VerifySection/ObfuscatedMessage.tsx b/src/components/home/VerifySection/ObfuscatedMessage.tsx
new file mode 100644
index 0000000..01ecc8c
--- /dev/null
+++ b/src/components/home/VerifySection/ObfuscatedMessage.tsx
@@ -0,0 +1,49 @@
+import React, { FunctionComponent, useEffect, useState } from 'react'
+import { isObfuscated } from '@trustvc/trustvc'
+
+interface ObfuscatedMessageProps {
+ document: unknown
+}
+
+export const ObfuscatedMessage: FunctionComponent = ({
+ document,
+}) => {
+ const [isDocumentObfuscated, setIsDocumentObfuscated] = useState<
+ boolean | null
+ >(null)
+
+ useEffect(() => {
+ let cancelled = false
+ const checkObfuscation = async () => {
+ try {
+ const result = await isObfuscated(document as any)
+ if (!cancelled) setIsDocumentObfuscated(result)
+ } catch (error) {
+ console.warn('Error checking if document is obfuscated:', error)
+ if (!cancelled) setIsDocumentObfuscated(false)
+ }
+ }
+ checkObfuscation()
+ return () => {
+ cancelled = true
+ }
+ }, [document])
+
+ if (isDocumentObfuscated === null || !isDocumentObfuscated) return null
+
+ return (
+
+ Note: There are fields/data obfuscated in this document.
+
+ )
+}
+
+export default ObfuscatedMessage
diff --git a/src/components/home/VerifySection/VerifyResult.test.tsx b/src/components/home/VerifySection/VerifyResult.test.tsx
index a4d6730..79405c6 100644
--- a/src/components/home/VerifySection/VerifyResult.test.tsx
+++ b/src/components/home/VerifySection/VerifyResult.test.tsx
@@ -18,6 +18,13 @@ vi.mock('./useVerify', async () => {
return { ...actual, makeExplorerAddressURL: vi.fn() }
})
+// Stub AssetManagementApplication to avoid DocumentProvider context dependency
+vi.mock('../../AssetManagementPanel/AssetManagementApplication', () => ({
+ AssetManagementApplication: () => (
+
+ ),
+}))
+
import { makeExplorerAddressURL } from './useVerify'
// ─── Helpers ──────────────────────────────────────────────────────────────────
diff --git a/src/components/home/VerifySection/VerifyResult.tsx b/src/components/home/VerifySection/VerifyResult.tsx
index f06d00f..726bdf2 100644
--- a/src/components/home/VerifySection/VerifyResult.tsx
+++ b/src/components/home/VerifySection/VerifyResult.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import NetworkTooltip from './NetworkTooltip'
import DocumentRenderer from './DocumentRenderer'
import InvalidAttachmentsBanner from './InvalidAttachmentsBanner'
+import ObfuscatedMessage from './ObfuscatedMessage'
import { makeExplorerAddressURL } from './useVerify'
import { CheckCircle, CrossCircle } from '../../common/Icons'
import { DocumentAttachment } from '../../../utils/helper'
@@ -21,6 +22,7 @@ interface VerifyResultProps {
tokenId?: string
issuer?: string
isTransferable?: boolean
+ isExpired?: boolean
tokenRegistryAddress?: string
tags?: string[]
owner?: { name?: string; address?: string }
@@ -57,6 +59,7 @@ const VerifyResult: React.FC = ({
onViewNftRegistry,
onViewEndorsementChain,
refreshEndorsementChain,
+ isExpired,
}) => {
const { changeNetwork, currentChainId } = useProviderContext()
@@ -306,6 +309,14 @@ const VerifyResult: React.FC = ({
)}
{/* Footer: Connect Wallet */}
+ {!isTransferable && (
+
+ )}
{isTransferable && tokenRegistryAddress && tokenId && (
= ({
refreshEndorsementChain={refreshEndorsementChain}
isTransferableDocument={isTransferable}
isSampleDocument={false}
- isExpired={false}
+ isExpired={isExpired}
/>
)}
+ {/* Obfuscation Notice */}
+ {rawDocument ?