DEV Community

Garret Parker
Garret Parker

Posted on

Craft NFTs on Algorand MainNet with IPFS Metadata via Pay-to-Pin SDK (No Pinata Required)

Craft NFTs (ASAs) on the Algorand blockchain with metadata pinned to IPFS via our pay-to-pin client SDK — no Pinata account, no API key, and no subscription required.

Result

A fully-on-chain MainNet NFT whose metadata is pinned to IPFS with 365-day timeboxed retention per payment, plus early renewal options.


Step 1: Install Dependencies

Run the following commands in your terminal to install the required packages:

# IPFS pay-to-pin SDK
pip install ipfs-pay-to-pin-client

# Algorand SDK
pip install algosdk

# Python 3.12+ required
python3 --version
Enter fullscreen mode Exit fullscreen mode

Step 2: The Complete Tutorial Script

Save the following code as create_nft.py:

#!/usr/bin/env python3
"""
Automated MainNet NFT Creation: Algorand ASA + IPFS Metadata
Uses ipfs-pay-to-pin-client SDK to pin metadata to IPFS.
No Pinata account required.

Workflow:
  1. Create NFT metadata JSON locally
  2. Pin to IPFS via pay-to-pin gateway (auto x402 payment via USDC)
  3. Create ASA on Algorand MainNet with CID attached as URL & metadata_hash
  4. Done — NFT is live on-chain with on-chain IPFS reference
"""

import json
import hashlib
import time

from algosdk import account, transaction
from algosdk.v2client import algod
from ipfs_pay_to_pin_client import IpfsPayToPinClient, PinResponse

# ============================================================
# CONFIGURATION (MAINNET)
# ============================================================

# Algorand MainNet Node (AlgoNode Free MainNet API)
ALGOD_NODE = "https://mainnet-api.algonode.cloud"
ALGOD_TOKEN = ""  # empty for free AlgoNode API

# Your Algorand MainNet wallet mnemonic
# ⚠️ NEVER hardcode or commit secret mnemonics! 
# Ensure this wallet has ALGO (for transaction fees/minimum balance) and USDC (for IPFS pinning).
WALLET_MNEMONIC = "your 25-word mainnet mnemonic phrase here"

# NFT details
NFT_NAME = "My First IPFS NFT"
UNIT_NAME = "IPFT"          # Ticker symbol
NFT_DESCRIPTION = "An NFT with metadata pinned via ipfs-pay-to-pin-client"
NFT_IMAGE_URL = "https://example.com/nft.png"

# Our pay-to-pin gateway (no API key needed!)
PAY_TO_PIN_GATEWAY = "https://pay-to-pin.duckdns.org"

# ============================================================
# STEP 1: Create NFT Metadata
# ============================================================

def create_nft_metadata(name, unit_name, description, image_url, attributes=None):
    """Create NFT metadata as a JSON object."""
    metadata = {
        "name": name,
        "description": description,
        "image": image_url,
        "attributes": attributes or [
            {"trait_type": "Type", "value": "Digital Art"},
            {"trait_type": "Network", "value": "Algorand MainNet"},
            {"trait_type": "Storage", "value": "IPFS via pay-to-pin"},
        ],
        "external_url": f"https://example.com/nft/{unit_name}"
    }
    return metadata

# ============================================================
# STEP 2: Pin Metadata to IPFS via ipfs-pay-to-pin-client SDK
# ============================================================

def pin_metadata_to_ipfs(metadata, sender_mnemonic, gateway_url=PAY_TO_PIN_GATEWAY):
    """
    Upload NFT metadata to IPFS using the pay-to-pin service.

    The SDK handles x402 payment automatically — you provide
    your Algorand wallet and it pays the gateway via USDC
    micropayment on MainNet. No Pinata account needed.

    Returns: PinResponse with the IPFS CID and 365-day pin expiration date.
    """
    client = IpfsPayToPinClient(
        gateway_url=gateway_url,
        sender_mnemonic=sender_mnemonic,
        preferred_network="algorand:mainnet"  # Pay with Algorand MainNet USDC
    )

    metadata_bytes = json.dumps(metadata, indent=2).encode("utf-8")

    pin_response: PinResponse = client.pin_bytes(
        data=metadata_bytes,
        filename="nft-metadata.json"
    )

    print(f"✅ Metadata pinned to IPFS!")
    print(f"   CID:        {pin_response.cid}")
    print(f"   Status:     {pin_response.status}")
    print(f"   Expires At: {pin_response.pin_expires_at}")
    print(f"   TX ID:      {pin_response.tx_id}")

    return pin_response

# ============================================================
# STEP 3: Create ASA with CID Attached
# ============================================================

def create_asa_with_cid(algod_client, sender_mnemonic, cid, nft_name, unit_name):
    """
    Create an ASA (NFT) on Algorand MainNet with the IPFS CID attached.
    """
    private_key = account.private_key_from_mnemonic(sender_mnemonic)
    sender_address = account.address_from_private_key(private_key)

    sp = algod_client.suggested_params()
    sp.flat_fee = True
    sp.fee = 1000  # 1000 microAlgos standard fee on MainNet

    cid_bytes = cid.encode("utf-8")
    metadata_hash = hashlib.sha512_256(cid_bytes).digest()  # 32 bytes

    print(f"\n📝 Metadata hash: {metadata_hash.hex()}")

    asa_txn = transaction.AssetConfigTxn(
        sender=sender_address,
        sp=sp,
        index=0,                   
        total=1,                    
        default_frozen=False,
        unit_name=unit_name,        
        asset_name=nft_name,        
        manager=sender_address,     
        reserve=sender_address,     
        freeze=sender_address,      
        clawback=sender_address,    
        url=f"ipfs://{cid}",        
        metadata_hash=metadata_hash, 
        decimals=0                   
    )

    signed_txn = asa_txn.sign(private_key)

    print(f"\n📤 Transaction ID: {signed_txn.transaction.get_txid()}")
    print(f"   Asset name: {nft_name}")
    print(f"   Unit name:  {unit_name}")
    print(f"   IPFS CID:   {cid}")

    return signed_txn

# ============================================================
# STEP 4: Submit & Confirm on MainNet
# ============================================================

def submit_transaction(algod_client, signed_txn):
    """Submit to Algorand MainNet and wait for confirmation."""
    tx_id = signed_txn.transaction.get_txid()
    algod_client.send_transactions([signed_txn])

    print(f"\n⏳ Waiting for MainNet confirmation...")

    for i in range(30):
        try:
            tx_info = algod_client.pending_transaction_info(tx_id)
            if tx_info.get("confirmed-round", 0) > 0:
                asset_id = tx_info.get("asset-index", 0)
                print(f"✅ NFT created on Algorand MainNet!")
                print(f"   Asset ID: {asset_id}")
                print(f"   Confirmed in round: {tx_info['confirmed-round']}")
                print(f"   Explorer: https://allo.info/tx/{tx_id}")
                print(f"   Asset:    https://allo.info/asset/{asset_id}")
                return asset_id
        except:
            pass
        time.sleep(1)

    raise TimeoutError("Transaction not confirmed after 30s")

# ============================================================
# MAIN — Run Everything
# ============================================================

def main():
    print("=" * 60)
    print("🎨 MainNet NFT Creation: Algorand ASA + IPFS Metadata")
    print("   Powered by ipfs-pay-to-pin-client SDK")
    print("   No Pinata account required.")
    print("=" * 60)

    print("\n📡 Connecting to Algorand MainNet...")
    algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_NODE)

    print("\n📝 Creating NFT metadata...")
    metadata = create_nft_metadata(
        name=NFT_NAME,
        unit_name=UNIT_NAME,
        description=NFT_DESCRIPTION,
        image_url=NFT_IMAGE_URL
    )
    print(f"   {json.dumps(metadata, indent=6)}")

    print("\n📌 Uploading metadata to IPFS via pay-to-pin gateway...")
    pin_response = pin_metadata_to_ipfs(
        metadata=metadata,
        sender_mnemonic=WALLET_MNEMONIC
    )

    print("\n🪙 Creating ASA (NFT) on Algorand MainNet...")
    signed_txn = create_asa_with_cid(
        algod_client=algod_client,
        sender_mnemonic=WALLET_MNEMONIC,
        cid=pin_response.cid,
        nft_name=NFT_NAME,
        unit_name=UNIT_NAME
    )

    print("\n🚀 Submitting to Algorand MainNet...")
    asset_id = submit_transaction(algod_client, signed_txn)

    print("\n" + "=" * 60)
    print("🎉 MAINNET NFT COMPLETE!")
    print("=" * 60)
    print(f"   Asset ID:   {asset_id}")
    print(f"   IPFS CID:   {pin_response.cid}")
    print(f"   Expires At: {pin_response.pin_expires_at}")
    print(f"   Metadata:   ipfs://{pin_response.cid}")
    print(f"   On-chain:   https://allo.info/asset/{asset_id}")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Step 3: Run the Tutorial

MainNet Execution

  1. Ensure your Algorand MainNet wallet has:
    • ALGO: ~0.1 ALGO minimum (for minimum balance requirements and ~0.001 ALGO tx fees).
    • USDC: A few cents in Algorand MainNet USDC (ASA ID 31566704) to pay the x402 IPFS pinning micropayment.
  2. Replace WALLET_MNEMONIC in the script with your wallet mnemonic.
  3. Run the script:
   python3 create_nft.py
Enter fullscreen mode Exit fullscreen mode

(Expected output will trace the successful creation of the metadata JSON, its IPFS pin confirmation, ASA creation, and network confirmation on MainNet).


Step 4: How It Works (Deep Dive)

The Three-Step Pipeline

Step 1: Metadata JSON              Step 2: Pin to IPFS              Step 3: Create ASA
┌──────────────────┐               ┌──────────────────┐             ┌──────────────────┐
│ {                │               │ ipfs-pay-to-pin  │             │ ASA Creation     │
│   "name": "...", │               │    SDK Client    │             │                  │
│   "description"  │ ─────────────▶│                  │ ─────────▶  │ URL: ipfs://CID  │
│   "image": "...",│               │  1. Upload JSON  │             │ Hash: SHA512/256 │
│   "attributes":  │               │  2. x402 payment │             │ Total: 1, Dec: 0 │
│     [...]        │               │  3. Get CID      │             │ Permissions: self│
│ }                │               │  4. Timeboxed pin│             └──────────────────┘
└──────────────────┘               └──────────────────┘                        │
                                                                               ▼
                                                                      Algorand MainNet Network
                                                                           ┌────────────┐
                                                                           │  ASA #123  │
                                                                           │ (NFT Live) │
                                                                           └────────────┘
Enter fullscreen mode Exit fullscreen mode

Why No Pinata?

Traditional NFT Metadata

  • Flow: NFT ➔ Pinata account ➔ API key ➔ Monthly subscription ($9–29/mo).
  • Risk: If you stop paying your monthly fee, your pin is dropped and lost.

With Our SDK

  • Flow: NFT ➔ ipfs-pay-to-pin-client SDK ➔ One-time x402 micropayment.
  • Retention & Renewal: Each payment pins your content for 365 days. Need to extend storage? Simply call the /renew endpoint for annual retention payments, which offers a 50% early renewal discount prior to expiration. No account creation, no API keys, and no lock-in.

Step 5: Renewal Endpoint Example

Extend the retention of any pinned file for another 365 days using your SDK:

def renew_pin_retention(cid, sender_mnemonic, gateway_url=PAY_TO_PIN_GATEWAY):
    """
    Renew IPFS retention for an existing CID for another 365 days.
    Early renewals prior to expiration qualify for a 50% discount.
    """
    client = IpfsPayToPinClient(
        gateway_url=gateway_url,
        sender_mnemonic=sender_mnemonic,
        preferred_network="algorand:mainnet"
    )

    renew_response = client.renew_pin(cid=cid)

    print(f"✅ Retention extended!")
    print(f"   CID:             {renew_response.cid}")
    print(f"   New Expiration:  {renew_response.pin_expires_at}")
    print(f"   Early Discount:  {renew_response.discount_applied}")
    return renew_response
Enter fullscreen mode Exit fullscreen mode

Step 6: Verification

Verify your NFT is properly linked to IPFS using this helper function:

def verify_nft(asset_id, cid, algod_client):
    """Verify the ASA metadata matches the IPFS CID on MainNet."""
    asset_info = algod_client.get_asset_by_id(asset_id)

    print(f"ASA Metadata:")
    print(f"  Name:          {asset_info['params']['name']}")
    print(f"  Unit Name:     {asset_info['params']['unit-name']}")
    print(f"  URL:           {asset_info['params']['url']}")
    print(f"  Metadata Hash: {asset_info['params']['metadata-hash']}")

    # Verify URL contains the CID
    expected_url = f"ipfs://{cid}"
    match = asset_info['params']['url'] == expected_url
    print(f"\n{'' if match else ''} URL matches CID: {match}")

    # Verify metadata hash
    expected_hash = hashlib.sha512_256(cid.encode()).digest()
    match = asset_info['params']['metadata-hash'] == expected_hash.hex()
    print(f"{'' if match else ''} Metadata hash verified: {match}")

    return match
Enter fullscreen mode Exit fullscreen mode

Step 7: Bulk NFT Creation

For efficiently minting multiple NFTs in a batch, you can adapt your approach:

def create_bulk_nfts(metadata_list, sender_mnemonic, algod_client, gateway_url=PAY_TO_PIN_GATEWAY):
    """Create multiple NFTs in a batch on MainNet."""
    client = IpfsPayToPinClient(
        gateway_url=gateway_url,
        sender_mnemonic=sender_mnemonic,
        preferred_network="algorand:mainnet"
    )

    pin_responses = []
    transactions = []
    private_key = account.private_key_from_mnemonic(sender_mnemonic)
    sender_address = account.address_from_private_key(private_key)
    sp = algod_client.suggested_params()
    sp.flat_fee = True

    for i, meta in enumerate(metadata_list):
        pin_resp = client.pin_bytes(
            data=json.dumps(meta).encode(),
            filename=f"nft-{i+1}.json"
        )
        pin_responses.append(pin_resp)

        cid_bytes = pin_resp.cid.encode("utf-8")
        metadata_hash = hashlib.sha512_256(cid_bytes).digest()

        txn = transaction.AssetConfigTxn(
            sender=sender_address, sp=sp, index=0, total=1,
            unit_name=f"NFT{i+1}", asset_name=meta["name"],
            manager=sender_address, reserve=sender_address,
            freeze=sender_address, clawback=sender_address,
            url=f"ipfs://{pin_resp.cid}", metadata_hash=metadata_hash, decimals=0
        )

        signed = txn.sign(private_key)
        transactions.append(signed)

    algod_client.send_transactions(transactions)
    print(f"✅ Created {len(transactions)} NFTs on MainNet!")

    for i, resp in enumerate(pin_responses):
        print(f"  NFT {i+1}: CID={resp.cid} TX={transactions[i].transaction.get_txid()}")

    return pin_responses
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Issue Cause Fix
InsufficientFundsError Wallet lacks ALGO for ASA creation or USDC for x402 payment. Fund your MainNet wallet with ALGO (~0.1) and USDC.
PaymentRequiredError x402 challenge returned (normal behavior). The SDK auto-handles this. Ensure sender_mnemonic has USDC.
PinningFailedError Gateway rejected upload. Check JSON validity, file size, and network connectivity.
Transaction not confirmed Network congestion or fee too low. Use standard sp.fee = 1000 (or higher if network busy).
URL mismatch CID format changed after pinning. Verify the CID matches between the pin response and the ASA URL.

Summary

Component Role
ipfs-pay-to-pin-client SDK Uploads metadata to IPFS, handles x402 micropayments via Algorand USDC, and supports 365-day retention & renewals.
Algorand ASA On-chain MainNet NFT containing the CID as the URL and the corresponding metadata hash.
IPFS Decentralized metadata storage solution with timeboxed retention.

Top comments (0)