Hartii developer docs

Gallery

Integrate

All chain examples use quais. RPC_URL_WITH_SHARD already includes /cyprus1, so providers are constructed with usePathing: false. Every address is checksummed before it reaches the RPC.

js
import { quais } from 'quais';

const RPC_URL = 'https://rpc.quai.network/cyprus1';
const provider = new quais.JsonRpcProvider(RPC_URL, undefined, { usePathing: false });

Browse the catalogue

js
const res = await fetch('https://hartiigallery.com/api/collections');
const { collections } = await res.json();
// [{ address, name, symbol, totalSupply, totalMinted, maxSupply, mintPrice, royaltyBps, ... }]

Mint from a collection

js
const COLLECTION_ABI = [
  'function mint() external payable',
  'function mintBatch(uint256 quantity) external payable',
  'function totalMinted() view returns (uint256)',
  'function totalSupply() view returns (uint256)',
];

async function mintOne(signer, collectionAddress, mintPriceWei) {
  const address = quais.getAddress(String(collectionAddress).toLowerCase());
  const collection = new quais.Contract(address, COLLECTION_ABI, signer);
  const tx = await collection.mint({ value: mintPriceWei });
  return tx.wait();
}

Deploy a new collection through the factory

js
const FACTORY_ADDRESS = '0x007d00B9752d852658A03167b825d62b68c375Fc'; // current factory
const FACTORY_ABI = [
  'function deployCollection(string _name, string _symbol, uint256 _mintPrice, uint256 _maxSupply, string _baseURI, uint256 _creatorThreshold, uint256 _royaltyBps, uint256 _maxPerWallet) external',
  'event CollectionDeployed(address indexed collection, address indexed creator, string name, string symbol, uint256 royaltyBps)',
];

async function deployCollection(signer, params) {
  const factory = new quais.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  const tx = await factory.deployCollection(
    params.name,
    params.symbol,
    params.mintPriceWei,
    params.maxSupply,
    params.baseURI,       // e.g. "ipfs://Qm..."
    params.creatorThresholdWei, // 0 disables Phase 2 holder revenue share
    params.royaltyBps,    // 0-800, immutable after this call
    params.maxPerWallet,  // 0 = unlimited
  );
  const receipt = await tx.wait();
  const deployed = receipt.logs
    .map((l) => { try { return factory.interface.parseLog(l); } catch { return null; } })
    .find((e) => e?.name === 'CollectionDeployed');
  return deployed.args.collection;
}

List an NFT for sale (fixed-price venue)

js
const NFT_ABI = ['function approve(address to, uint256 tokenId) external'];
const MARKET_ADDRESS = '0x006792f8913d99e2D9E72087D4C3CBAf2e77E867'; // HartiiMarketplaceV8
const MARKET_ABI = [
  'function listNFT(address collection, uint256 tokenId, uint256 price) external',
  'function buyNFT(uint256 listingId) external payable',
  'function previewFees(uint256 listingId) view returns (uint256 platformFee, uint256 royaltyAmount, uint256 sellerProceeds)',
];

async function listForSale(signer, collectionAddress, tokenId, priceWei) {
  const nft = new quais.Contract(quais.getAddress(collectionAddress.toLowerCase()), NFT_ABI, signer);
  // One-signature listing works ONLY for collections deployed through the current factory
  // (it pre-approves the marketplace at mint time). Anything else needs this approve() first.
  await (await nft.approve(MARKET_ADDRESS, tokenId)).wait();

  const market = new quais.Contract(MARKET_ADDRESS, MARKET_ABI, signer);
  const tx = await market.listNFT(quais.getAddress(collectionAddress.toLowerCase()), tokenId, priceWei);
  return tx.wait();
}

Buy a listed NFT

js
async function buyListing(signer, listingId, priceWei) {
  const market = new quais.Contract(MARKET_ADDRESS, MARKET_ABI, signer);
  const tx = await market.buyNFT(listingId, { value: priceWei });
  return tx.wait(); // reverts "Listing expired" / "Not active" / "Insufficient payment" on failure
}

buyNFT is nonReentrant and whenNotPaused; overpayment is refunded automatically in the same transaction (no separate claim step needed for the buyer's change).

Read live floor prices

js
const res = await fetch('https://hartiigallery.com/api/floor-prices');
const { collections, partial, failedMarketplaces } = await res.json();
if (partial) {
  // some marketplace venues failed to read — collections[] may be missing entries, not zero-value ones
  console.warn('floor prices partial, failed venues:', failedMarketplaces);
}

Resolve an image/metadata URL through the caching proxy

js
function imgUrl(ipfsOrHttpUrl, width) {
  const u = encodeURIComponent(ipfsOrHttpUrl);
  return `https://hartiigallery.com/api/img?u=${u}${width ? `&w=${width}` : ''}`;
}
function metaUrl(ipfsOrHttpUrl) {
  return `https://hartiigallery.com/api/meta?u=${encodeURIComponent(ipfsOrHttpUrl)}`;
}
Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.