Skip to content

Commit da80829

Browse files
author
LUKSOAgent
committed
Add NFT.Storage integration for real IPFS uploads
1 parent 04df2b3 commit da80829

4 files changed

Lines changed: 131 additions & 16 deletions

File tree

frontend/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# NFT.Storage API Key for IPFS uploads
2+
# Get yours at: https://nft.storage/
3+
VITE_NFT_STORAGE_API_KEY=572d0deb.447fabfc6b204c35a5b991e9393eba7d

frontend/src/utils/ipfs.ts

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,136 @@
11
import { IPFS_CONFIG } from './constants'
22

3-
// Simple IPFS upload function using a public gateway for demo
4-
// In production, use a proper IPFS client or pinning service
3+
const NFT_STORAGE_API = 'https://api.nft.storage'
4+
5+
/**
6+
* Upload data to IPFS via NFT.Storage
7+
*/
58
export async function uploadToIPFS(data: any): Promise<string> {
6-
// For demo purposes, we'll simulate an IPFS hash
7-
// In production, use ipfs-http-client or a pinning service like Pinata
8-
const jsonString = JSON.stringify(data)
9-
const encoder = new TextEncoder()
10-
const dataBuffer = encoder.encode(jsonString)
9+
const apiKey = import.meta.env.VITE_NFT_STORAGE_API_KEY || ''
1110

12-
// Create a hash-like string (this is just for demo)
13-
const hash = await crypto.subtle.digest('SHA-256', dataBuffer)
14-
const hashArray = Array.from(new Uint8Array(hash))
15-
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
11+
if (!apiKey) {
12+
console.warn('NFT.Storage API key not found, using mock hash')
13+
// Fallback to mock for development
14+
const jsonString = JSON.stringify(data)
15+
const encoder = new TextEncoder()
16+
const dataBuffer = encoder.encode(jsonString)
17+
const hash = await crypto.subtle.digest('SHA-256', dataBuffer)
18+
const hashArray = Array.from(new Uint8Array(hash))
19+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
20+
return `Qm${hashHex.slice(0, 44)}`
21+
}
22+
23+
try {
24+
const response = await fetch(`${NFT_STORAGE_API}/upload`, {
25+
method: 'POST',
26+
headers: {
27+
'Authorization': `Bearer ${apiKey}`,
28+
'Content-Type': 'application/json',
29+
},
30+
body: JSON.stringify(data),
31+
})
32+
33+
if (!response.ok) {
34+
const error = await response.text()
35+
throw new Error(`NFT.Storage upload failed: ${error}`)
36+
}
37+
38+
const result = await response.json()
39+
40+
// NFT.Storage returns the CID in result.value.cid
41+
const cid = result.value?.cid || result.cid
42+
43+
if (!cid) {
44+
throw new Error('No CID returned from NFT.Storage')
45+
}
46+
47+
console.log('Uploaded to IPFS:', cid)
48+
return cid
49+
} catch (error) {
50+
console.error('IPFS upload error:', error)
51+
throw error
52+
}
53+
}
54+
55+
/**
56+
* Upload a file to IPFS via NFT.Storage
57+
*/
58+
export async function uploadFileToIPFS(file: File): Promise<string> {
59+
const apiKey = import.meta.env.VITE_NFT_STORAGE_API_KEY || ''
1660

17-
// Return IPFS-style hash (Qm... format simulation)
18-
return `Qm${hashHex.slice(0, 44)}`
61+
if (!apiKey) {
62+
throw new Error('NFT.Storage API key not configured')
63+
}
64+
65+
try {
66+
const formData = new FormData()
67+
formData.append('file', file)
68+
69+
const response = await fetch(`${NFT_STORAGE_API}/upload`, {
70+
method: 'POST',
71+
headers: {
72+
'Authorization': `Bearer ${apiKey}`,
73+
},
74+
body: formData,
75+
})
76+
77+
if (!response.ok) {
78+
const error = await response.text()
79+
throw new Error(`NFT.Storage upload failed: ${error}`)
80+
}
81+
82+
const result = await response.json()
83+
const cid = result.value?.cid || result.cid
84+
85+
if (!cid) {
86+
throw new Error('No CID returned from NFT.Storage')
87+
}
88+
89+
console.log('File uploaded to IPFS:', cid)
90+
return cid
91+
} catch (error) {
92+
console.error('IPFS file upload error:', error)
93+
throw error
94+
}
1995
}
2096

2197
export function getIPFSUrl(hash: string): string {
22-
return `${IPFS_CONFIG.gateway}/${hash}`
98+
// Use ipfs.io gateway (reliable, no rate limits for viewing)
99+
return `https://ipfs.io/ipfs/${hash}`
100+
}
101+
102+
export function getIPFSGatewayUrl(hash: string, gateway: string = 'https://ipfs.io'): string {
103+
return `${gateway}/ipfs/${hash}`
23104
}
24105

25106
export async function fetchFromIPFS(hash: string): Promise<any> {
26107
try {
27-
const response = await fetch(getIPFSUrl(hash))
108+
const url = getIPFSUrl(hash)
109+
const response = await fetch(url)
28110
if (!response.ok) throw new Error('Failed to fetch from IPFS')
29111
return await response.json()
30112
} catch (error) {
31113
console.error('IPFS fetch error:', error)
32114
throw error
33115
}
34116
}
117+
118+
/**
119+
* Check if a CID is available on IPFS
120+
*/
121+
export async function checkIPFSAvailability(hash: string): Promise<boolean> {
122+
try {
123+
const controller = new AbortController()
124+
const timeoutId = setTimeout(() => controller.abort(), 5000)
125+
126+
const response = await fetch(getIPFSUrl(hash), {
127+
method: 'HEAD',
128+
signal: controller.signal,
129+
})
130+
131+
clearTimeout(timeoutId)
132+
return response.ok
133+
} catch {
134+
return false
135+
}
136+
}

frontend/vercel.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"buildCommand": "pnpm run build",
3+
"outputDirectory": "dist",
4+
"env": {
5+
"VITE_NFT_STORAGE_API_KEY": "572d0deb.447fabfc6b204c35a5b991e9393eba7d"
6+
}
7+
}

frontend/vite.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ export default defineConfig({
1212
build: {
1313
outDir: 'dist',
1414
sourcemap: true
15-
}
15+
},
16+
define: {
17+
'import.meta.env.VITE_NFT_STORAGE_API_KEY': JSON.stringify(process.env.VITE_NFT_STORAGE_API_KEY || ''),
18+
},
1619
})

0 commit comments

Comments
 (0)