From e1ac2c0e047bd1660c957847d0bfec3728e35a38 Mon Sep 17 00:00:00 2001 From: SaraJane Date: Tue, 20 Jan 2026 02:17:10 +0000 Subject: [PATCH] fix --- .../NFT_mint_server/app.js | 116 ++++++++---------- .../src/components/TokenizeAsset.tsx | 6 +- 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/projects/TokenizeRWATemplate-contracts/NFT_mint_server/app.js b/projects/TokenizeRWATemplate-contracts/NFT_mint_server/app.js index a42e5b2..b2f159d 100644 --- a/projects/TokenizeRWATemplate-contracts/NFT_mint_server/app.js +++ b/projects/TokenizeRWATemplate-contracts/NFT_mint_server/app.js @@ -1,99 +1,88 @@ -// server.js (ESM) — deployable on Vercel import pinataSDK from '@pinata/sdk' import cors from 'cors' import dotenv from 'dotenv' import express from 'express' import multer from 'multer' -import path from 'path' import { Readable } from 'stream' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) // Load local .env for dev. In Vercel, env vars come from platform. -dotenv.config({ path: path.resolve(__dirname, '.env') }) +dotenv.config() const app = express() /** * CORS - * - Allows localhost for local dev - * - Allows your main frontend via FRONTEND_ORIGIN + * - Allows localhost dev + * - Allows your main frontend + * - Allows ANY *.vercel.app (so forks work for non-technical founders) + * + * Optional: set ALLOWED_ORIGINS in Vercel env as comma-separated list + * Example: + * ALLOWED_ORIGINS=https://tokenize-rwa-template.vercel.app,http://localhost:5173 */ -const allowedOrigins = [ - 'http://localhost:5173', - process.env.FRONTEND_ORIGIN, // e.g. https://tokenize-rwa-template.vercel.app -] +const explicitAllowed = (process.env.ALLOWED_ORIGINS || '') + .split(',') + .map((s) => s.trim()) .filter(Boolean) - .map((o) => o.trim()) + +const isAllowedOrigin = (origin: string) => { + // explicitly allowed + if (explicitAllowed.includes(origin)) return true + + // local dev + if (origin === 'http://localhost:5173') return true + + // allow any Vercel preview/prod frontend (great for forks) + try { + const host = new URL(origin).hostname + if (host.endsWith('.vercel.app')) return true + } catch { + // ignore + } + + return false +} app.use( cors({ origin: (origin, cb) => { - // allow server-to-server / curl (no origin) + // allow server-to-server / curl / same-origin (no Origin header) if (!origin) return cb(null, true) - - if (allowedOrigins.includes(origin)) return cb(null, true) - - // Allow any frontend on vercel.app (great for forks + previews) - try { - const host = new URL(origin).hostname - if (host.endsWith('.vercel.app')) return cb(null, true) - } catch { - // ignore URL parse errors - } - + if (isAllowedOrigin(origin)) return cb(null, true) return cb(null, false) }, methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], - credentials: false, }), ) -// Preflight for all routes +// Handle preflight requests for ALL routes app.options('*', cors()) -// For non-multipart endpoints (multer handles multipart/form-data) -app.use(express.json({ limit: '15mb' })) +app.use(express.json()) // Pinata client -const pinata = process.env.PINATA_JWT - ? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT }) - : new pinataSDK(process.env.PINATA_API_KEY, process.env.PINATA_API_SECRET) +const pinata = + process.env.PINATA_JWT && process.env.PINATA_JWT.trim().length > 0 + ? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT }) + : new pinataSDK(process.env.PINATA_API_KEY || '', process.env.PINATA_API_SECRET || '') -// Optional: test credentials at cold start -;(async () => { - try { - if (typeof pinata.testAuthentication === 'function') { - await pinata.testAuthentication() - console.log('Pinata auth OK') - } else { - console.log('Pinata SDK loaded (no testAuthentication method)') - } - } catch (e) { - console.error('Pinata authentication FAILED. Check env vars.', e?.message || e) - } -})() - -// health -app.get('/health', (_req, res) => { - res.set('Cache-Control', 'no-store') - res.status(200).json({ ok: true, ts: Date.now() }) -}) - -// uploads +// Uploads (multipart/form-data) const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 }, // 10MB }) -function safeTrim(v) { +app.get('/health', (_req, res) => { + res.set('Cache-Control', 'no-store') + res.status(200).json({ ok: true, ts: Date.now() }) +}) + +function safeTrim(v: unknown) { return typeof v === 'string' ? v.trim() : '' } -function safeJsonParse(v, fallback) { +function safeJsonParse(v: unknown, fallback: any) { try { if (typeof v !== 'string' || !v.trim()) return fallback return JSON.parse(v) @@ -107,14 +96,13 @@ app.post('/api/pin-image', upload.single('file'), async (req, res) => { const file = req.file if (!file) return res.status(400).json({ error: 'No file uploaded' }) - // Optional multipart fields from frontend + // Optional form fields (friendly for vibe-coders) const metaName = safeTrim(req.body?.metaName) || 'NFT Example' - const metaDescription = safeTrim(req.body?.metaDescription) || 'This is an unchangeable NFT' + const metaDescription = safeTrim(req.body?.metaDescription) || 'Pinned via TokenizeRWA template' const properties = safeJsonParse(req.body?.properties, {}) // Pin image - const stream = Readable.from(file.buffer) - // Pinata SDK sometimes expects a .path/filename + const stream = Readable.from(file.buffer) as any stream.path = file.originalname || 'upload' const imageResult = await pinata.pinFileToIPFS(stream, { @@ -138,8 +126,12 @@ app.post('/api/pin-image', upload.single('file'), async (req, res) => { const metadataUrl = `ipfs://${jsonResult.IpfsHash}` return res.status(200).json({ metadataUrl }) - } catch (error) { - const msg = error?.response?.data?.error || error?.response?.data || error?.message || 'Failed to pin to IPFS.' + } catch (error: any) { + const msg = + error?.response?.data?.error || + error?.response?.data || + error?.message || + 'Failed to pin to IPFS.' return res.status(500).json({ error: msg }) } }) diff --git a/projects/TokenizeRWATemplate-frontend/src/components/TokenizeAsset.tsx b/projects/TokenizeRWATemplate-frontend/src/components/TokenizeAsset.tsx index 4b2804e..75af013 100644 --- a/projects/TokenizeRWATemplate-frontend/src/components/TokenizeAsset.tsx +++ b/projects/TokenizeRWATemplate-frontend/src/components/TokenizeAsset.tsx @@ -12,7 +12,7 @@ import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClien * Captures ASA configuration including compliance fields */ type CreatedAsset = { - assetId: bigint + assetId: string assetName: string unitName: string total: string @@ -575,7 +575,7 @@ export default function TokenizeAsset() { const assetId = createResult.assetId const newEntry: CreatedAsset = { - assetId: BigInt(assetId), + assetId: String(assetId), assetName: String(assetName), unitName: String(unitName), total: String(total), @@ -900,7 +900,7 @@ export default function TokenizeAsset() { // ✅ Persist minted NFT into SAME history list (NFTs are ASAs) const nftEntry: CreatedAsset = { - assetId: BigInt(assetId), + assetId: String(assetId), assetName: String(nftName), unitName: String(nftUnit), total: String(nftSupply),