Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions functions/.env.phishblock-demo

This file was deleted.

4 changes: 3 additions & 1 deletion functions/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules/
*.local
*.local

.env.phishblock-demo
100 changes: 84 additions & 16 deletions functions/index.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,118 @@
const { onDocumentUpdated } = require("firebase-functions/v2/firestore");
const { defineString } = require("firebase-functions/params");
const logger = require("firebase-functions/logger");
const { initializeApp } = require("firebase-admin/app");
const { getFirestore } = require("firebase-admin/firestore");
const { ethers } = require("ethers");
import { onDocumentUpdated } from "firebase-functions/v2/firestore";
import { defineString } from "firebase-functions/params";
import * as logger from "firebase-functions/logger";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { ethers } from "ethers";
import { Web3Storage, File } from "web3.storage";
import fetch from "node-fetch";

initializeApp();

// ✅ NEW UPPERCASE KEYS
// ✅ Blockchain environment variables
const RPC_URL = defineString("BLOCKCHAIN_RPC");
const PRIVATE_KEY = defineString("BLOCKCHAIN_PRIVATE_KEY");
const CONTRACT_ADDRESS = defineString("BLOCKCHAIN_CONTRACT_ADDRESS");

// ✅ IPFS / Web3.Storage environment variables
const NFT_STORAGE_TOKEN = defineString("NFT_STORAGE_TOKEN");
const PINATA_JWT = defineString("PINATA_JWT");

const CONTRACT_ABI = [
"function anchor(bytes32 _hash, string calldata postId) public returns (bool)"
];

exports.onPostUpdate = onDocumentUpdated(
export const onPostUpdate = onDocumentUpdated(
{
document: "posts/{postId}",
region: "asia-south1",
database: "(default)",
},
async (event) => {
const newData = event.data.after.data();
const oldData = event.data.before.data();
const before = event.data.before.data();
const after = event.data.after.data();
const postId = event.params.postId;

if (!oldData.anchored && newData.upvotes >= 10) {
const oldVotes = before.upvotes || 0;
const newVotes = after.upvotes || 0;

if (!before.anchored && oldVotes < 10 && newVotes >= 10) {
logger.info(`🚀 [START] Anchoring + Archiving for post ${postId} (upvotes=${newVotes})`);

const firestore = getFirestore();
const postRef = firestore.collection("posts").doc(postId);

try {
logger.info("🧩 Connecting to Polygon RPC...");
const provider = new ethers.JsonRpcProvider(RPC_URL.value());
const wallet = new ethers.Wallet(PRIVATE_KEY.value(), provider);
const contract = new ethers.Contract(CONTRACT_ADDRESS.value(), CONTRACT_ABI, wallet);
const sender = await wallet.getAddress();
logger.info(`✅ Connected as ${sender}`);

const hash = ethers.id(postId);
const tx = await contract.anchor(hash, postId);
await tx.wait();
logger.info(`🚀 Transaction sent: ${tx.hash}`);
const receipt = await tx.wait();
logger.info(`✅ Confirmed in block ${receipt.blockNumber}`);

await getFirestore().collection("posts").doc(postId).update({
await postRef.update({
anchored: true,
anchorTx: tx.hash,
anchorTxUrl: `https://amoy.polygonscan.com/tx/${tx.hash}`,
anchoredAt: new Date(),
});

// --- IPFS ARCHIVING ---
logger.info("📦 Uploading metadata to Web3.Storage...");
const client = new Web3Storage({ token: NFT_STORAGE_TOKEN.value() });
const metadata = {
url: after.url,
description: after.description,
author: after.authorName || "Anonymous",
createdAt: after.createdAt || new Date().toISOString(),
anchorTx: tx.hash,
anchorTxUrl: `https://amoy.polygonscan.com/tx/${tx.hash}`,
};
const buffer = Buffer.from(JSON.stringify(metadata));
const files = [new File([buffer], "metadata.json")];

let cid;
try {
cid = await client.put(files);
logger.info(`✅ Web3.Storage CID: ${cid}`);
} catch (uploadErr) {
logger.warn("⚠️ Web3.Storage failed, trying Pinata...");
const res = await fetch("https://api.pinata.cloud/pinning/pinJSONToIPFS", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${PINATA_JWT.value()}`,
},
body: JSON.stringify(metadata),
});
const data = await res.json();
cid = data.IpfsHash;
logger.info(`✅ Pinata CID: ${cid}`);
}

await postRef.update({
archiveCid: cid,
archivedAt: new Date(),
ipfsMetadata: `https://w3s.link/ipfs/${cid}`,
ipfsGateway: `https://gateway.pinata.cloud/ipfs/${cid}`,
backupLink: `https://w3s.link/ipfs/${cid}`,
evidenceHash: hash,
});

logger.info(`🎯 Archival complete for ${postId}`);
} catch (err) {
logger.error("Blockchain anchor failed:", err);
logger.error(`❌ Error processing post ${postId}:`, err);
await postRef.update({
anchoringError: true,
lastError: err.message || "Unexpected failure",
lastErrorAt: new Date(),
});
}
}
}
);

Loading