@@ -17,131 +17,10 @@ import ReactMarkdown from 'react-markdown';
1717import remarkGfm from "remark-gfm" ;
1818import { Badge } from "@/components/ui/badge" ;
1919import { CheckCircle2 , XCircle , Clock , Calendar , Coins , Hash , FileText , Wallet } from "lucide-react" ;
20-
21- const hasUsableJsonMetadata = ( value : any ) : boolean =>
22- Boolean ( value && typeof value === "object" && value . body && typeof value . body === "object" ) ;
23-
24- const getAnchorUrls = ( anchorUrl : string ) : string [ ] => {
25- if ( ! anchorUrl ) return [ ] ;
26- if ( ! anchorUrl . startsWith ( "ipfs://" ) ) {
27- return [ anchorUrl ] ;
28- }
29- const cidPath = anchorUrl . replace ( "ipfs://" , "" ) ;
30- return [
31- `https://ipfs.io/ipfs/${ cidPath } ` ,
32- `https://cloudflare-ipfs.com/ipfs/${ cidPath } ` ,
33- `https://dweb.link/ipfs/${ cidPath } ` ,
34- ] ;
35- } ;
36-
37- async function fetchJsonFromUrl ( url : string ) : Promise < any > {
38- const controller = new AbortController ( ) ;
39- const timeout = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ;
40- try {
41- const res = await fetch ( url , {
42- method : "GET" ,
43- headers : { Accept : "application/json" } ,
44- signal : controller . signal ,
45- } ) ;
46- if ( ! res . ok ) {
47- throw new Error ( `HTTP ${ res . status } ${ res . statusText } ` ) ;
48- }
49- const text = await res . text ( ) ;
50- return JSON . parse ( text ) ;
51- } finally {
52- clearTimeout ( timeout ) ;
53- }
54- }
55-
56- async function hydrateMetadataFromAnchor ( rawMetadata : any , txHash : string , certIndex : number ) : Promise < any > {
57- if ( hasUsableJsonMetadata ( rawMetadata ?. json_metadata ) ) {
58- return rawMetadata ;
59- }
60- const anchorUrl =
61- typeof rawMetadata ?. url === "string" && rawMetadata . url . length > 0
62- ? rawMetadata . url
63- : null ;
64- if ( ! anchorUrl ) return rawMetadata ;
65-
66- const candidateUrls = getAnchorUrls ( anchorUrl ) ;
67- for ( const url of candidateUrls ) {
68- try {
69- const anchorJson = await fetchJsonFromUrl ( url ) ;
70- const jsonMetadata = anchorJson ?. body ? anchorJson : { body : anchorJson } ;
71- if ( hasUsableJsonMetadata ( jsonMetadata ) ) {
72- return { ...rawMetadata , json_metadata : jsonMetadata } ;
73- }
74- } catch {
75- // Try next URL candidate.
76- }
77- }
78- return rawMetadata ;
79- }
80-
81- const normalizeProposalMetadata = (
82- rawMetadata : any ,
83- txHash : string ,
84- certIndex : number ,
85- ) : ProposalMetadata => {
86- const rawBody = rawMetadata ?. json_metadata ?. body ;
87- const body = rawBody && typeof rawBody === "object" ? rawBody : { } ;
88- const rawAuthors = rawMetadata ?. json_metadata ?. authors ;
89- const authors = Array . isArray ( rawAuthors )
90- ? rawAuthors
91- . map ( ( author ) =>
92- typeof author ?. name === "string" ? { name : author . name } : null ,
93- )
94- . filter ( ( author ) : author is { name : string } => Boolean ( author ) )
95- : [ ] ;
96- const references = Array . isArray ( ( body as any ) . references )
97- ? ( body as any ) . references . filter (
98- ( ref : any ) =>
99- ref &&
100- typeof ref === "object" &&
101- typeof ref . label === "string" &&
102- typeof ref . uri === "string" &&
103- typeof ref [ "@type" ] === "string" ,
104- )
105- : [ ] ;
106-
107- return {
108- tx_hash :
109- typeof rawMetadata ?. tx_hash === "string" ? rawMetadata . tx_hash : txHash ,
110- cert_index :
111- Number . isFinite ( Number ( rawMetadata ?. cert_index ) )
112- ? Number ( rawMetadata . cert_index )
113- : certIndex ,
114- governance_type :
115- typeof rawMetadata ?. governance_type === "string"
116- ? rawMetadata . governance_type
117- : "" ,
118- hash : typeof rawMetadata ?. hash === "string" ? rawMetadata . hash : "" ,
119- url : typeof rawMetadata ?. url === "string" ? rawMetadata . url : "" ,
120- bytes : typeof rawMetadata ?. bytes === "string" ? rawMetadata . bytes : "" ,
121- json_metadata : {
122- body : {
123- title :
124- typeof ( body as any ) . title === "string"
125- ? ( body as any ) . title
126- : "Metadata could not be loaded." ,
127- abstract :
128- typeof ( body as any ) . abstract === "string"
129- ? ( body as any ) . abstract
130- : `${ txHash } #${ certIndex } ` ,
131- motivation :
132- typeof ( body as any ) . motivation === "string"
133- ? ( body as any ) . motivation
134- : "" ,
135- rationale :
136- typeof ( body as any ) . rationale === "string"
137- ? ( body as any ) . rationale
138- : "" ,
139- references,
140- } ,
141- authors,
142- } ,
143- } ;
144- } ;
20+ import {
21+ createProposalMetadataFallback ,
22+ fetchProposalMetadataWithFallback ,
23+ } from "@/lib/governance/proposalMetadata" ;
14524
14625function WalletGovernanceProposalContent ( { id } : { id : string } ) {
14726 const network = useSiteStore ( ( state ) => state . network ) ;
@@ -186,40 +65,19 @@ function WalletGovernanceProposalContent({ id }: { id: string }) {
18665 . get ( detailsPath )
18766 . catch ( ( ) => null ) as Promise < ProposalDetails | null > ;
18867
189- // Try primary metadata path first.
190- let metadata : ProposalMetadata | null = null ;
191- const metadataPath = `/governance/proposals/${ txHash } /${ certIndex } /metadata` ;
192- try {
193- metadata = ( await hydrateMetadataFromAnchor (
194- await blockchainProvider . get ( metadataPath ) ,
195- txHash ,
196- Number ( certIndex ) ,
197- ) ) as ProposalMetadata ;
198- } catch {
199-
200- // Fallback: use details.id (gov_action_id) endpoint when available.
201- const detailsForFallback = await detailsPromise ;
202- const govActionId =
203- detailsForFallback && typeof detailsForFallback . id === "string"
204- ? detailsForFallback . id
205- : null ;
206-
207- if ( govActionId ) {
208- const fallbackPath = `/governance/proposals/${ govActionId } /metadata` ;
209- try {
210- metadata = ( await hydrateMetadataFromAnchor (
211- await blockchainProvider . get ( fallbackPath ) ,
212- txHash ,
213- Number ( certIndex ) ,
214- ) ) as ProposalMetadata ;
215- } catch {
216- // Use default metadata below.
217- }
218- }
219- }
68+ const proposal = {
69+ tx_hash : txHash ,
70+ cert_index : Number ( certIndex ) ,
71+ governance_type : "" ,
72+ } ;
73+ const metadata = await fetchProposalMetadataWithFallback ( {
74+ provider : blockchainProvider ,
75+ proposal,
76+ fetchDetails : ( ) => detailsPromise ,
77+ } ) ;
22078
22179 setProposalMetadata (
222- normalizeProposalMetadata ( metadata , txHash , Number ( certIndex ) ) ,
80+ metadata ?? createProposalMetadataFallback ( proposal ) ,
22381 ) ;
22482
22583 // Fetch proposal details
0 commit comments