import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { createWalletClient, createPublicClient, http, parseEventLogs } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import type { Address } from 'viem';
import { chiliz, spicy } from 'viem/chains';
import { PinataSDK } from 'pinata';
// Minimal ABI: safeMint(to, uri) + tokenURI + Transfer
const abi = [
{ type: 'function', name: 'safeMint', stateMutability: 'nonpayable',
inputs: [{ name: 'to', type: 'address' }, { name: 'uri', type: 'string' }], outputs: [] },
{ type: 'function', name: 'tokenURI', stateMutability: 'view',
inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [{ type: 'string' }] },
{ type: 'event', name: 'Transfer',
inputs: [
{ name: 'from', type: 'address', indexed: true },
{ name: 'to', type: 'address', indexed: true },
{ name: 'tokenId', type: 'uint256', indexed: true },
]},
] as const;
function guessMime(p: string) {
const ext = path.extname(p).toLowerCase();
if (ext === '.png') return 'image/png';
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
if (ext === '.webp') return 'image/webp';
if (ext === '.gif') return 'image/gif';
if (ext === '.mp4') return 'video/mp4';
if (ext === '.webm') return 'video/webm';
return 'application/octet-stream';
}
async function main() {
// Choose your network: use `spicy` for Spicy Testnet, switch to `chiliz` for Chiliz Chain Mainnet.
const chain = spicy; // Change to `chiliz` for Mainnet
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({ account, chain, transport: http() });
const publicClient = createPublicClient({ chain, transport: http() });
const address = process.env.CONTRACT_ADDRESS as Address;
const recipient = (process.env.RECIPIENT as Address) || account.address;
// IPFS upload (Pinata)
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: process.env.PINATA_GATEWAY!,
});
const filePath = process.env.IMAGE_PATH!;
const fileBlob = new Blob([fs.readFileSync(filePath)], { type: guessMime(filePath) });
const fileObj = new File([fileBlob], path.basename(filePath), { type: guessMime(filePath) });
const up = await pinata.upload.public.file(fileObj);
const imageUri = `ipfs://${up.cid}`;
const meta = await pinata.upload.public.json({
name: process.env.NAME!,
description: process.env.DESCRIPTION!,
image: imageUri,
});
const metadataUri = `ipfs://${meta.cid}`;
// Mint on-chain via viem
const txHash = await walletClient.writeContract({
address,
abi,
functionName: 'safeMint',
args: [recipient, metadataUri],
});
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
const ZERO = '0x0000000000000000000000000000000000000000';
const logs = parseEventLogs({ abi, logs: receipt.logs, eventName: 'Transfer' });
const mintLog = logs.find(l => (l.args as any).from?.toLowerCase?.() === ZERO);
const tokenId = mintLog ? (mintLog.args as any).tokenId : undefined;
console.log('tx:', txHash, '| tokenId:', tokenId ?? '(not parsed)', '| tokenURI:', metadataUri);
}
main().catch(err => (console.error(err), process.exit(1)));