diff --git a/src/app/service-providers/data/column-definition.tsx b/src/app/service-providers/data/column-definition.tsx
index 921e6fcd..185e2c5e 100644
--- a/src/app/service-providers/data/column-definition.tsx
+++ b/src/app/service-providers/data/column-definition.tsx
@@ -1,9 +1,9 @@
import { ID } from '@filecoin-foundation/ui-filecoin/Table/ID'
import { YesNoStatus } from '@filecoin-foundation/ui-filecoin/Table/YesNoStatus'
-// import { ExternalTextLink } from '@filecoin-foundation/ui-filecoin/TextLink/ExternalTextLink'
import { createColumnHelper } from '@tanstack/react-table'
import { CompactPeerID } from '@/components/CompactPeerID'
+import { PdpScanLink } from '@/components/PdpScanLink'
import { ProviderOverview } from '@/components/ProviderOverview'
import { SoftwareVersion } from '@/components/SoftwareVersion'
@@ -52,15 +52,16 @@ export const columns = [
sortingFn: sortSoftwareVersion,
sortUndefined: 'last',
}),
- // TODO: Add check activity link
- // columnHelper.accessor('checkActivityUrl', {
- // header: 'Check Activity',
- // cell: (info) => (
- //
- // View on PDP Scan
- //
- // ),
- // }),
+ columnHelper.accessor('checkActivityUrl', {
+ header: 'Check Activity',
+ cell: (info) => (
+
+ ),
+ enableSorting: false,
+ }),
// TODO: Add Service Offered Column
columnHelper.accessor('serviceStatus', {
header: 'Status',
diff --git a/src/app/warm-storage-service/data/column-definition.tsx b/src/app/warm-storage-service/data/column-definition.tsx
index 5bb0a2be..073deec8 100644
--- a/src/app/warm-storage-service/data/column-definition.tsx
+++ b/src/app/warm-storage-service/data/column-definition.tsx
@@ -1,8 +1,8 @@
import { ID } from '@filecoin-foundation/ui-filecoin/Table/ID'
-// import { ExternalTextLink } from '@filecoin-foundation/ui-filecoin/TextLink/ExternalTextLink'
import { createColumnHelper } from '@tanstack/react-table'
import { CompactPeerID } from '@/components/CompactPeerID'
+import { PdpScanLink } from '@/components/PdpScanLink'
import { ProviderOverview } from '@/components/ProviderOverview'
import { SoftwareVersion } from '@/components/SoftwareVersion'
@@ -45,15 +45,16 @@ export const columns = [
sortingFn: sortSoftwareVersion,
sortUndefined: 'last',
}),
- // TODO: Add check activity link
- // columnHelper.accessor('checkActivityUrl', {
- // header: 'Check Activity',
- // cell: (info) => (
- //
- // View on PDP Scan
- //
- // ),
- // }),
+ columnHelper.accessor('checkActivityUrl', {
+ header: 'Check Activity',
+ cell: (info) => (
+
+ ),
+ enableSorting: false,
+ }),
columnHelper.accessor('location', {
header: 'Location',
cell: (info) => info.getValue(),
diff --git a/src/components/PdpScanLink.tsx b/src/components/PdpScanLink.tsx
new file mode 100644
index 00000000..f1fb3332
--- /dev/null
+++ b/src/components/PdpScanLink.tsx
@@ -0,0 +1,17 @@
+import { ExternalTextLink } from '@filecoin-foundation/ui-filecoin/TextLink/ExternalTextLink'
+
+type PdpScanLinkProps = {
+ pdpScanUrl: string
+ providerName: string
+}
+
+export function PdpScanLink({ pdpScanUrl, providerName }: PdpScanLinkProps) {
+ return (
+
+ View on PDP Scan
+
+ )
+}
diff --git a/src/schemas/provider-schema.ts b/src/schemas/provider-schema.ts
index 151e07d1..25b3e075 100644
--- a/src/schemas/provider-schema.ts
+++ b/src/schemas/provider-schema.ts
@@ -9,7 +9,7 @@ const ethereumAddressSchema = z.custom
(
export const providerSchema = z.object({
capabilityKeys: z.array(z.string()),
capacityTb: z.bigint().optional(),
- // checkActivityUrl: z.url().optional(), // TODO: Remove optional
+ checkActivityUrl: z.url(),
description: z.string(),
id: z.number(),
ipniIpfs: z.boolean(),
diff --git a/src/services/providers/contract.ts b/src/services/providers/contract.ts
index 6686e824..21c34374 100644
--- a/src/services/providers/contract.ts
+++ b/src/services/providers/contract.ts
@@ -4,7 +4,7 @@ import type { ServiceRegistryABI, WarmStorageViewABI } from '@/config/abis'
import { BATCH_SIZE, PDP_PRODUCT_TYPE, PROVIDER_FETCH_LIMIT } from './constants'
import { processProviderData } from './processor'
-import type { ProviderWithoutSoftwareVersion } from './types'
+import type { BaseProviderData } from './types'
/**
* Fetch approved provider IDs from WarmStorageView contract with pagination
@@ -50,8 +50,8 @@ export async function fetchProvidersBulk(
>,
onlyActive: boolean,
approvedProviderIds?: Set,
-): Promise {
- const providers: ProviderWithoutSoftwareVersion[] = []
+): Promise {
+ const providers: BaseProviderData[] = []
let offset = 0
let hasMore = true
@@ -117,7 +117,7 @@ export async function fetchProviderById(
PublicClient
>,
isApproved = false,
-): Promise {
+): Promise {
const providerData =
await serviceRegistryContract.read.getProviderWithProduct([
providerId,
diff --git a/src/services/providers/index.ts b/src/services/providers/index.ts
index 2cfcc9b8..44bded9a 100644
--- a/src/services/providers/index.ts
+++ b/src/services/providers/index.ts
@@ -12,6 +12,7 @@ import {
type ServiceProvider,
} from '@/schemas/provider-schema'
import type { FetchProvidersOptions, ProviderFilter } from '@/types/providers'
+import { getCheckActivityUrl } from '@/utils/provider-urls'
import { VERSION_FETCH_CONCURRENCY } from './constants'
import {
@@ -19,7 +20,7 @@ import {
fetchProviderById,
fetchProvidersBulk,
} from './contract'
-import type { ProviderWithoutSoftwareVersion } from './types'
+import type { BaseProviderData } from './types'
import { fetchSoftwareVersion } from './version'
/**
@@ -34,7 +35,7 @@ async function fetchProvidersByFilter(
PublicClient
>
},
-): Promise {
+): Promise {
const { storageView, serviceRegistry } = contracts
// Fetch approved provider IDs for marking providers
@@ -70,8 +71,7 @@ async function fetchProvidersByFilter(
const providers = await Promise.all(providerPromises)
return providers.filter(
- (provider): provider is ProviderWithoutSoftwareVersion =>
- provider !== null,
+ (provider): provider is BaseProviderData => provider !== null,
)
}
@@ -79,14 +79,13 @@ async function fetchProvidersByFilter(
}
/**
- * Enrich providers with software version information
+ * Enrich providers with additional information (software version and check activity URL)
*/
-async function enrichProvidersWithVersions(
- providers: ProviderWithoutSoftwareVersion[],
+async function enrichProviders(
+ providers: BaseProviderData[],
+ network: Network,
): Promise {
- const providersWithVersions: Array<
- ProviderWithoutSoftwareVersion & { softwareVersion?: string }
- > = []
+ const providersWithVersions: ServiceProvider[] = []
// Process providers in batches
for (let i = 0; i < providers.length; i += VERSION_FETCH_CONCURRENCY) {
@@ -94,7 +93,11 @@ async function enrichProvidersWithVersions(
const batchResults = await Promise.all(
batch.map(async (provider) => {
const softwareVersion = await fetchSoftwareVersion(provider.serviceUrl)
- return { ...provider, softwareVersion }
+ const checkActivityUrl = getCheckActivityUrl(
+ network,
+ provider.payeeAddress,
+ )
+ return { ...provider, softwareVersion, checkActivityUrl }
}),
)
providersWithVersions.push(...batchResults)
@@ -178,6 +181,6 @@ export async function fetchProviders(
serviceRegistry,
})
- // Enrich with software versions
- return enrichProvidersWithVersions(fetchedProviders)
+ // Enrich with additional information
+ return enrichProviders(fetchedProviders, network)
}
diff --git a/src/services/providers/processor.ts b/src/services/providers/processor.ts
index 83968523..ea6d1cb6 100644
--- a/src/services/providers/processor.ts
+++ b/src/services/providers/processor.ts
@@ -5,7 +5,7 @@ import type { ProviderData } from '@/types/contract-types'
import { decodePDPCapabilities } from '@/utils/decode-pdp-capabilities'
import { parseLocation } from '@/utils/parse-location'
-import type { ProviderWithoutSoftwareVersion } from './types'
+import type { BaseProviderData } from './types'
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
@@ -40,7 +40,7 @@ function buildCapabilities(
export function processProviderData(
providerData: ProviderData,
isApproved = false,
-): ProviderWithoutSoftwareVersion | undefined {
+): BaseProviderData | undefined {
const { providerId, providerInfo, product, productCapabilityValues } =
providerData
diff --git a/src/services/providers/types.ts b/src/services/providers/types.ts
index ddbe67ed..45f8c700 100644
--- a/src/services/providers/types.ts
+++ b/src/services/providers/types.ts
@@ -1,9 +1,9 @@
import type { ServiceProvider } from '@/schemas/provider-schema'
/**
- * Provider without software version (before version fetch)
+ * Base provider data from contract (before enrichment with software version and check activity URL)
*/
-export type ProviderWithoutSoftwareVersion = Omit<
+export type BaseProviderData = Omit<
ServiceProvider,
- 'softwareVersion'
+ 'softwareVersion' | 'checkActivityUrl'
>
diff --git a/src/utils/provider-urls.ts b/src/utils/provider-urls.ts
new file mode 100644
index 00000000..1851d110
--- /dev/null
+++ b/src/utils/provider-urls.ts
@@ -0,0 +1,18 @@
+import type { Address } from 'viem'
+
+import type { Network } from '@/config/chains'
+import { EXPLORERS } from '@/constants/external-services'
+
+/**
+ * Constructs the check activity URL for a provider on PDP Scan
+ *
+ * @param network - The network (mainnet or calibration)
+ * @param payeeAddress - The provider's payee address
+ * @returns The full URL to view provider activity on PDP Scan
+ */
+export function getCheckActivityUrl(
+ network: Network,
+ payeeAddress: Address,
+): string {
+ return `${EXPLORERS.PDP_SCAN[network]}${payeeAddress}`
+}