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.
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
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
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
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)
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
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
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
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)}`;
}