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
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()
Step 3: Run the Tutorial
MainNet Execution
- 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.
- Replace
WALLET_MNEMONICin the script with your wallet mnemonic. - Run the script:
python3 create_nft.py
(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) │
└────────────┘
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-clientSDK ➔ One-time x402 micropayment. -
Retention & Renewal: Each payment pins your content for 365 days. Need to extend storage? Simply call the
/renewendpoint 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
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
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
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)