A soulbound NFT certificate system built on Paseo Asset Hub testnet. Issue non-transferable credentials that are permanently bound to recipient wallets.
- Soulbound NFTs - Certificates cannot be transferred once issued (ERC-5192)
- Role-Based Access - Admin and Issuer roles control certificate management
- IPFS Metadata - Certificate files stored on IPFS via Pinata
- On-Chain Verification - Anyone can verify certificate authenticity
- Revocation Support - Issuers can revoke certificates when needed
├── contracts/ # Solidity smart contract
├── frontend/ # Next.js web application
├── ignition/ # Hardhat Ignition deployment modules
├── scripts/ # CLI interaction scripts
└── test/ # Contract test suite
- Node.js 18+
- A Polkadot-compatible wallet (Talisman, SubWallet, or Polkadot.js)
- PAS tokens for testnet transactions
# Install root dependencies (contracts)
npm install
# Install frontend dependencies
cd frontend && npm installnpm run test# Copy environment file and add your private key
cp .env.example .env
# Deploy to Paseo testnet
npm run deploy:paseocd frontend
npm run devOpen http://localhost:3000 in your browser.
An ERC-721 NFT with soulbound mechanics:
| Function | Access | Description |
|---|---|---|
registerParticipant(address, name) |
Issuer | Register address to receive certificates |
issueCertificate(address, tokenURI, contentHash, cohort) |
Issuer | Mint soulbound certificate |
revokeCertificate(tokenId) |
Issuer | Revoke a certificate |
verifyCertificate(tokenId) |
Public | Verify certificate validity |
getParticipantCertificates(address) |
Public | Get all certificates for an address |
DEFAULT_ADMIN_ROLE- Can grant/revoke rolesISSUER_ROLE- Can register participants, issue and revoke certificates
| Property | Value |
|---|---|
| Network | Paseo Asset Hub |
| Chain ID | 420420422 |
| RPC URL | https://testnet-passet-hub-eth-rpc.polkadot.io |
| Block Explorer | https://blockscout-passet-hub.parity-testnet.parity.io |
| Currency | PAS |
0xC8c1D0AB32eb290D44C6edc60790934518A4a08A - View on Blockscout
- Register Participant - Issuer registers wallet address with participant name
- Upload to IPFS - Upload certificate file to Pinata, receive URI and content hash
- Issue Certificate - Issuer mints soulbound NFT with metadata
- Verify - Anyone can verify certificate by token ID
- Create account at Pinata
- Generate JWT from API Keys
- Enter JWT in the frontend's "Issue Certificate" tab
# Set contract address in .env
CONTRACT_ADDRESS=0xC8c1D0AB32eb290D44C6edc60790934518A4a08A
# Run interaction script
npx hardhat run scripts/interact.js --network paseo- Solidity 0.8.22
- OpenZeppelin Contracts 4.9.6
- Hardhat + Hardhat Ignition
- Next.js 16 / React 19
- TypeScript 5
- Tailwind CSS 4
- ethers.js 6
- Talisman Connect
| Command | Description |
|---|---|
npm run compile |
Compile Solidity contracts |
npm run test |
Run test suite |
npm run deploy:local |
Deploy to local Hardhat network |
npm run deploy:paseo |
Deploy to Paseo testnet |
| Command | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Build for production |
npm run start |
Start production server |
npm run lint |
Run ESLint |
MIT