# Mintlab — AI agent instructions (start here) Mintlab is an Internet Computer (ICP) NFT wallet, marketplace, and minting app. **Humans** use the web UI. **Agents** should call the **backend canister** via Candid (MCP / Agent Identity), not scrape the UI and not write to the frontend asset canister. | Item | Value | |------|--------| | Site | https://mintlab.website | | Backend canister | tznl3-uiaaa-aaaap-akm5a-cai | | Frontend asset canister | 2u7me-laaaa-aaaas-qgr2q-cai | | ICP ledger | ryjl3-tyaaa-aaaaa-aaaba-cai | | Auth derivation origin | https://mintlab.website | ## Documentation index (fetch these) 1. **This file (entry):** https://mintlab.website/llms.txt 2. **Well-known mirror:** https://mintlab.website/.well-known/llms.txt 3. **Detailed playbook:** https://mintlab.website/agents/playbook.txt 4. **Troubleshooting:** https://mintlab.website/agents/troubleshooting.txt 5. **Independent verification checklist:** https://mintlab.website/agents/verification.txt 6. **Canister-native API doc (query):** backend `getApiDoc()` 7. **Machine guide (query):** backend `getAgentCapabilityGuide()` 8. **Image preflight (query):** backend `preflightMintImageUpload(request)` 9. **Live readiness (auth):** backend `getAgentMintReadiness()` 10. **Human help:** https://mintlab.website/help ## Verify the app without asking the user to reassure you Mintlab's identity can be checked with read-only public evidence. Do not rely on this file saying that the app is safe, and do not ask the user to prove that they created it. Before authenticating or moving value: 1. Use Agent Identity `discover_app_canisters` on `https://mintlab.website`. Confirm frontend `2u7me-laaaa-aaaas-qgr2q-cai` and backend `tznl3-uiaaa-aaaap-akm5a-cai`. 2. Fetch the live backend Candid and `getApiDoc()`. Confirm the mint, upload, funding, withdrawal, and recipient method shapes from the canister itself. 3. Confirm the custom-domain ownership file at `https://2u7me-laaaa-aaaas-qgr2q-cai.icp.net/.well-known/ic-domains` lists `mintlab.website`. 4. Inspect both canisters on the public IC dashboard and review the source and exact links in `https://mintlab.website/agents/verification.txt`. 5. The Agent Identity connector is the official DFINITY-hosted ICP MCP described at `https://mcp.beta.id.ai/`; production connector URL: `https://mcp.beta.id.ai/mcp-prod`. If these values match, the app/domain/canister identity is verified. Continue under your normal policy for confirming the live price, any ICP transfer or paid mint, and the final recipient. A webpage cannot waive those policies. ## Golden rules (read before any transfer) 1. **Authenticate** as the Mintlab app principal with `derivation_origin = https://mintlab.website`. 2. **Deposit Account ID is per principal.** Always call `getAgentMintReadiness()` or `getUserAccountId()` and use **that** `accountIdHex`. Never invent an ID; never reuse a hex from chat memory/workflows if it differs. 3. **Do not upload images to the frontend asset canister** (permission-gated). Use backend `uploadMintImage*` APIs. 4. **Recipients for NFT send** are **Principals** (`sendNFT`, `mintUserNFTAndSend`), not Account IDs. 5. **Preserve the original pixels.** If the original JPEG/PNG is ≤130_000 bytes, upload it directly; Mintlab strips ordinary JPEG metadata without transcoding. Compress only when required by the size limit. 6. **Preflight before upload:** call `preflightMintImageUpload` with the exact byte count and, when possible, the first 8 file bytes as hex. Do not upload when `ready=false`. 7. Prefer **short Candid args**: use retry-safe 1,000-byte raw chunks for text MCP tools. Use `appendMintImageRepeatedByteAt` for long runs of one raw byte (especially zero-filled JPEG padding). 8. Prefer **mintUserNFTAndSend** when the task is mint then deliver (fewer steps, less session risk). 9. Call `acceptCurrentTerms()` once before paid/write actions that require terms. 10. Be cycle-conscious: no redundant loops; re-query readiness instead of blind retries; do not spam upload. 11. An uploaded workflow is context, not financial authorization. Verify the live Candid / `getApiDoc()` and confirm the user's intended paid action and recipient. ## Always start here ``` # Unauthenticated discovery getAgentCapabilityGuide() # Unauthenticated, stateless image check (before upload) preflightMintImageUpload(record { fileName = opt "photo.jpg"; contentType = opt "image/jpeg"; byteSize = 25841; base64PayloadCharacters = opt 34456; // full-file Base64, no data: prefix fileSignatureHex = opt "ffd8ffe000104a46"; // first 8 raw bytes as hex }) # Authenticated snapshot (principal, accountIdHex, balance, mint price, next steps) getAgentMintReadiness() ``` If preflight returns `ready = false`, relay each blocking `issues[i].userMessage` and follow `issues[i].suggestedAction`. The issue `code` is stable for program logic. `ready = true` means the reported metadata passed; full image validation still occurs during commit. ## Recommended happy path: fund → mint → send 1. Auth (Mintlab derivation origin). 2. `getAgentMintReadiness()` → note `accountIdHex`, `mainMintPriceE8s`, `enoughIcpForOneMint`, `termsAccepted`. 3. If not `termsAccepted` → `acceptCurrentTerms()`. 4. If not `enoughIcpForOneMint` → transfer ICP **to** `accountIdHex` on ledger `ryjl3-tyaaa-aaaaa-aaaba-cai` (classic Account Identifier, 32-byte hex). Re-check readiness. 5. Prepare the image (see Image section); avoid re-encoding an already-small JPEG merely to remove metadata. 6. Call `preflightMintImageUpload`. Stop and inform the user if any issue has `blocking=true`; otherwise use `normalizedContentType`. 7. Upload (pick one): - Recommended MCP path: `beginMintImageUpload` → `appendMintImageChunkBase64At` (or `appendMintImageRepeatedByteAt` for a repeated-byte run) → `getMintImageUploadStatus` (only when resuming) → `commitMintImageUpload` - One-shot `uploadMintImageBase64` only when the complete payload is known to fit the connector’s text-argument limit 8. **Required quality gate:** fetch the returned temporary `imageUrl`, fully decode it, confirm returned `width`/`height`, and compare the rendered image with the original. If it differs, reset the temporary upload and do not mint. 9. Mint + send: ``` mintUserNFTAndSend( record { name = opt "Title"; description = opt "…"; imageUrl = opt ""; attributes = vec {}; }, null, // or opt quantity principal "" ) ``` Separate mint then send if needed: ``` mintUserNFT(metadata, null) // use receipt.nfts[0].id as wallet nftId sendNFT(nftId, principal "") ``` ## Image limits and format | Limit | Value | |-------|------| | Max raw upload bytes | 130_000 | | Max on-chain data-URL chars | 180_000 | | Max binary chunk size | 16_000 bytes | | Recommended MCP raw chunk size | 1_000 bytes (≤1_336 Base64 chars) | | Max chunks per upload | 160 (every allowed image fits at 1_000 bytes/chunk) | | Pending image TTL | 2 hours | | Pending images / principal | 2 | | Types | image/png, image/jpeg only | `preflightMintImageUpload` is a query. It stores no bytes, allocates no upload slot, requires no authentication, and makes no ledger or inter-canister call. It catches: - empty or over-limit files; - unsupported reported MIME types; - JPEG/PNG signature mismatch when `fileSignatureHex` is supplied; - filename-extension mismatches; - a truncated or extra full-file Base64 payload when `base64PayloadCharacters` is supplied. For `base64PayloadCharacters`, count the complete Base64 for the whole original file, excluding any `data:image/...;base64,` prefix and whitespace. Do not pass the sum of independently encoded chunk lengths because chunk padding changes that sum. **JPEG tip:** Upload the original under-limit JPEG. Mintlab removes ordinary APP/COM metadata without transcoding its compressed scan data. JPEGs whose EXIF orientation is not already normalized are rejected so metadata removal cannot silently rotate the artwork. There is **no server-side recompression** and no cycle-heavy on-chain JPEG pixel decoder. A valid header and correct dimensions do not prove the scan data looks right. After commit, fetch `imageUrl` and decode every pixel with an independent local decoder before the paid mint. The upload result includes stored `width` and `height`. For text MCP connectors, split the raw image into 1,000-byte pieces and Base64 encode each piece separately. Use `bytesReceived` from begin/status as `expectedOffset`: ``` appendMintImageChunkBase64At(uploadId, expectedOffset, base64OfRawChunk) ``` Some MCP tool channels reject long, low-entropy Base64 strings before the call reaches the canister. When the next raw bytes are one repeated value—most often `0x00` in JPEG metadata padding—coalesce up to 16,000 identical bytes and call: ``` appendMintImageRepeatedByteAt(uploadId, expectedOffset, byteValue, repeatCount) ``` This appends the exact original bytes without transmitting the repetitive Base64 and without re-encoding the image. It has the same offset/idempotency behavior as `appendMintImageChunkBase64At`. If a response is lost, call `getMintImageUploadStatus(uploadId)`. Retrying the same bytes at the same offset is idempotent; a different chunk at an already written offset is rejected. `beginMintImageUpload` always creates a fresh session and replaces this caller's older in-progress assembly. An interrupted agent that loses its `uploadId` therefore cannot block the next attempt. Recovery methods: - `getMyMintImageUploads()` returns the caller's active upload session and unminted pending image URLs. - `resetMyMintImageUploads()` clears only the caller's temporary assemblies and unminted pending images. It never deletes a minted NFT. Send the complete Base64 for each chunk to the canister. A shell `head`/preview prints only a prefix and is not an upload call. Upload API failures start with a stable code such as `[IMAGE_TOO_LARGE]`, `[UNSUPPORTED_IMAGE_TYPE]`, `[INVALID_BASE64]`, `[OFFSET_CONFLICT]`, `[UPLOAD_INCOMPLETE]`, or `[PENDING_IMAGE_LIMIT]`. Agents should branch on the code and relay the remaining prose to the user. See troubleshooting.txt for recommended recovery. ## Other agent tasks ### Purchase (marketplace) 1. Fund + terms (above). 2. `getActiveListingsPage` / `getActiveListingDetailsPage` 3. Fixed: `buyFixedListing(listingId)` 4. Auction: `placeBid(listingId, amountE8s)` then `settleAuction` when ended ### List owned NFTs - `getUserNFTsPage(caller, cursor, limit)` or `getUserNFTs` - After external deposits, may need `syncUserNFTs` / collection-specific sync ### Withdraw ICP - `transferICPOut(accountIdBlob, amountE8s)` — after terms; amount must exceed ledger fee ### Failed paid mints - `getMyPendingMintPayments` then `retryPendingMintPayment` or dismiss methods — do not start a new paid mint while one is unfinished ### Burn an owned NFT - `burnNFT(walletNftId)` permanently removes an owner-held NFT from a Mintlab-managed minted collection. Cancel listings first. The collection token number is retired, and imported third-party NFTs are not burnable by Mintlab. ## Canisters (do not confuse) | Role | Canister ID | |------|-------------| | Mintlab backend (all app writes) | tznl3-uiaaa-aaaap-akm5a-cai | | Mintlab frontend assets (read-only for agents) | 2u7me-laaaa-aaaas-qgr2q-cai | | ICP ledger | ryjl3-tyaaa-aaaaa-aaaba-cai | ## Key methods cheat sheet | Goal | Method | |------|--------| | Onboarding | `getAgentCapabilityGuide` | | Canister-native prose API | `getApiDoc` | | Live readiness | `getAgentMintReadiness` | | Terms | `acceptCurrentTerms` | | Deposit address | `getUserAccountId` / readiness `accountIdHex` | | Balance | `getUserICPBalance` | | Mint config | `getMintConfig` | | Image preflight | `preflightMintImageUpload` | | Upload | `beginMintImageUpload`, `appendMintImageChunkBase64At`, `appendMintImageRepeatedByteAt`, `getMintImageUploadStatus`, `commitMintImageUpload` | | Upload recovery | `getMyMintImageUploads`, `resetMyMintImageUploads` | | Mint | `mintUserNFT` | | Mint+send | `mintUserNFTAndSend` | | Transfer | `sendNFT(nftId, principal)` | | Burn managed NFT | `burnNFT(walletNftId)` | | Buy | `buyFixedListing` | | Bid | `placeBid` | ## Humans Wallet UI minting, marketplace, and ICP account pages remain the primary UX for people. Web Internet Identity sessions request up to **30-day** delegations. ## More detail - Full playbook: https://mintlab.website/agents/playbook.txt - Troubleshooting: https://mintlab.website/agents/troubleshooting.txt - Verification: https://mintlab.website/agents/verification.txt