# Ekx Pinata Ipfs

> IPFS pinning with Pinata for NFT metadata and media — JWT auth, uploading files and JSON, dedicated gateways, and the phased-reveal metadata pattern used by property NFTs. Use when uploading NFT metadata or images, building a tokenURI, or debugging slow or missing IPFS content.

- Skill: `ekinoxis-evm/ekx-pinata-ipfs` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ekinoxis-evm/ekx-pinata-ipfs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ekinoxis-evm/ekx-pinata-ipfs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Ekinoxis-evm (https://skillmd.com/u/ekinoxis-evm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ekinoxis-evm/ekx-pinata-ipfs

---


# Pinata / IPFS

Our NFT metadata and media storage, including the storage target behind property
NFT metadata.

Docs: https://docs.pinata.cloud

---

## Environment

```bash
PINATA_JWT=                     # SECRET — the modern auth, use this
PINATA_API_KEY=                 # legacy pair
PINATA_SECRET_KEY=              # SECRET, legacy
NEXT_PUBLIC_PINATA_GATEWAY=     # your-gateway.mypinata.cloud
```

⚠️ The portfolio contains `PINATA`, `PINATA_APIKEY`, `PINATASECRET` and
`PINATA_SECRET_KEY` as separate names across repos. Standardise on `PINATA_JWT` alone —
the key/secret pair is legacy and the JWT replaces both.

---

## Uploading

```ts
// Server-side only — the JWT can pin to your paid account.
const form = new FormData();
form.append("file", new Blob([buffer]), "house-42.png");

const res = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PINATA_JWT}` },
  body: form,
});
const { IpfsHash } = await res.json();       // the CID
```

JSON metadata: `POST https://api.pinata.cloud/pinning/pinJSONToIPFS`.

**Never expose the JWT to the browser.** Anyone with it can pin arbitrary data to
your billed account. Upload through a route handler that authorises the user first.

---

## tokenURI

Store `ipfs://<CID>` onchain, not a gateway URL. Gateways change; the CID does not,
and a hardcoded gateway in an immutable contract is a permanent dependency on one vendor.

```solidity
function tokenURI(uint256 id) public view override returns (string memory) {
    return string.concat("ipfs://", _cid[id]);
}
```

Resolve to a gateway in the frontend:

```ts
export const ipfsToHttp = (uri: string) =>
  uri.replace("ipfs://", `https://${process.env.NEXT_PUBLIC_PINATA_GATEWAY}/ipfs/`);
```

**Use the dedicated gateway.** The public `gateway.pinata.cloud` is heavily rate-limited
and slow enough that NFT images visibly fail to load in a grid.

---

## Phased reveal

`HouseNFT` reveals metadata in phases as an auction progresses. The pattern:

1. Pin the full metadata set up front, keep the CIDs private.
2. Store only the current phase's CID onchain.
3. Advance the phase with an owner-only setter as the auction moves.

**Content on IPFS is public the moment it is pinned** — the CID is the only thing
hiding it, and CIDs are guessable if derived predictably. If a phase must be genuinely
secret, do not pin it until reveal, and commit to a hash onchain instead.

---

## Gotchas

1. **JWT in the browser** — billable account takeover.
2. **Public gateway** is too slow for a grid of images.
3. **Gateway URL onchain** is a permanent vendor lock. Use `ipfs://`.
4. **Pinned ≠ private.** Anything pinned is retrievable by CID.
5. **Unpinning does not delete** — other nodes may still hold it.
6. **Large files** need the resumable upload endpoint, not a single POST.
7. **Metadata JSON must match the ERC-721 schema** (`name`, `description`, `image`) or marketplaces render nothing.

