Mintline
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Mintlineassay mint So11111111111111111111111111111111111111112 for identity collisions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Mintline
Prove which Solana token came first.
A public, hash-chained origin registry for Solana token identities. It decodes on-chain metadata, finds semantic collisions with an open-weight model that runs in your browser, and files every claim in a tamper-evident ledger anyone can replay.
Live app · Source · API · Agent · Issues

Real output. Wrapped SOL assayed against live Solana data, scored 52.5. The stamp ink is driven by the score.
The problem
Solana launchpads mint thousands of tokens a day. Copying an established project's name and ticker is trivial, costs nothing, and is hard to spot — the copy looks like a brand-new asset until someone loses money.
Today the answer is "ask in the group chat". That does not scale, does not produce evidence, and nobody can check it later.
Mintline turns that into a registry: on-chain identity is decoded, compared against everything already claimed, scored with a published formula, and sealed into a chain that a third party can replay without trusting this app.
Related MCP server: mint-protocol
✨ Features
Decodes the chain itself. Derives the Metaplex metadata account for a mint with hand-rolled ed25519 curve maths and borsh decoding — no indexer key, no wallet, no SDK. Source
Runs the model on your machine. A 22M-parameter sentence-embedding model loads in the browser via ONNX Runtime Web. The token's identity text never leaves the device, and it keeps working with no network after the first load. Source
Always has a deterministic floor. The server compares with a lexical n-gram comparator that needs no model and no network, so a score is always reproducible. Whichever comparator ran is named in the response, the UI and every dossier.
Publishes its arithmetic. Six weighted factors, itemised, each with the raw measured value it was computed from. Weights are in the API response and in the export.
Refuses to fake certainty. A factor that could not be read contributes zero and flags the result
degraded. Weights are never rebalanced around a gap, so a partial computation cannot look like a confident one.Seals every action. Create, verdict, re-assay, share and retire each append to a per-record SHA-384 chain. Replay recomputes every seal and names the first broken link. Source
Scriptable by agents. Ten typed MCP tools over JSON-RPC 2.0. The three mutating tools call the same service layer the UI calls, and they are idempotent on a supplied key. Console
Leaves you with a dossier. Download the score, the factor table, the per-source attribution with fetch timestamps, the full seal list and the disclaimer as Markdown or JSON.
🚀 Quickstart
git clone https://github.com/aniruddhaadak80/mintline.git
cd mintline
npm install
npm run devOpen http://localhost:3000. Zero environment variables are required — with
no DATABASE_URL the app runs on an embedded PGlite database in
.mintline-data/, and the full loop (CRUD, engine, MCP, replay, export) works.
Verify it end to end, against a real production build:
npm run build
node scripts/smoke.mjs # boots its own server, walks the whole journeyProduction environment variables
Variable | Required | Purpose |
| yes in production | Neon Postgres connection string, reached through the Neon HTTP driver. |
| recommended | Signs the anonymous session cookie. Without it a random per-process key is generated, so sessions do not survive a redeploy. |
| recommended | Canonical origin for metadata, OpenGraph, sitemap and share links. |
See .env.example. Values are never committed.
🔌 API
Every endpoint is JSON, every error uses the same envelope, and every response is scoped to the caller's anonymous session.
# Is the app healthy, and which store actually answered?
curl -s https://mintline-eight.vercel.app/api/health | jq
# Live, normalised chain facts for a mint, with attribution.
curl -s "https://mintline-eight.vercel.app/api/mint?mint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" \
| jq '{name: .metadata.name, liquidity: .liquidityUsd, holders: .holders.state,
sources: [.sources[] | {id, status, fetchedAt}]}'
# Run the engine without persisting anything.
curl -s -X POST https://mintline-eight.vercel.app/api/assay \
-H 'content-type: application/json' \
-d '{"mint":"So11111111111111111111111111111111111111112"}' \
| jq '{score: .result.score, verdict: .result.verdict, degraded: .result.degraded,
factors: [.result.factors[] | {key, weight, value, points}]}'A mutation, then a read-back, then the decision and the chain:
# 1. Create. The assay is computed server-side from live chain data,
# so a stored record can never carry a score you supplied.
curl -s -c jar -X POST https://mintline-eight.vercel.app/api/origins \
-H 'content-type: application/json' \
-d '{"mint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","name":"Example claim","idempotencyKey":"demo-1"}' \
| jq '{id: .origin.id, score, verdict, head: .origin.chainHead}'
ID=o_... # take .origin.id from the response
# 2. Read it back.
curl -s -b jar "https://mintline-eight.vercel.app/api/origins/$ID" | jq '.origin.status'
# 3. Record a decision. This appends a sealed event.
curl -s -b jar -X PATCH "https://mintline-eight.vercel.app/api/origins/$ID" \
-H 'content-type: application/json' -d '{"status":"disputed","note":"metadata overlaps"}' \
| jq '{status: .origin.status, events: .eventCount, head: .chainHead}'
# 4. Re-run the engine against current chain data.
curl -s -b jar -X POST "https://mintline-eight.vercel.app/api/origins/$ID/assay" \
| jq '{score: .result.score, verificationRef}'
# 5. Replay the chain.
curl -s -b jar "https://mintline-eight.vercel.app/api/integrity/replay?id=$ID" \
| jq '{ok, checked, brokenAtSeq, headSeal}'
# 6. Export the dossier, then retire the claim.
curl -s -b jar "https://mintline-eight.vercel.app/api/export?id=$ID&format=md" -o dossier.md
curl -s -b jar -X DELETE "https://mintline-eight.vercel.app/api/origins/$ID" \
| jq '{tombstone, replayable, eventCount}'Errors
{ "error": { "code": "invalid_request", "message": "mint must be a base58 Solana address",
"details": { "field": "mint" } } }400 malformed input · 404 absent or owned by another session · 409
conflict · 429 rate limited · 500 generic, never leaking internals.
Agent interface
MCP-style JSON-RPC 2.0 over HTTP POST at
/api/mcp, protocol 2025-06-18. Ten
tools: five read-only (assay_mint, get_live_market, list_origins,
get_origin, verify_integrity, export_dossier), one analysis, three
mutating (register_origin, record_verdict, share_origin) and one
destructive (retire_origin).
curl -s -X POST https://mintline-eight.vercel.app/api/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
curl -s -X POST https://mintline-eight.vercel.app/api/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
"name":"assay_mint",
"arguments":{"mint":"So11111111111111111111111111111111111111112"}}}' \
| jq '.result.structuredContent | {score, verdict, comparator}'Point a client at /mcp.json, which
carries the live endpoint. Try it in the browser at
/agent — every button there is a real
request and prints the real response, including JSON-RPC errors.
📁 Project map
User routes
Route | Purpose |
| Entry. The assay press, the verdict bands, and the published weights. |
| Workspace. Filter, sort, page and register claims. Query state lives in the URL, so a view is shareable. |
| Dynamic. One specimen: factor table, nearest registered identities, decisions, share link, and the full audit chain with a replay button. |
| Analysis bench. Strike any mint, compare both comparators, read the documented weights. |
| Integrity. Replays every visible chain and reports the first broken link. |
| MCP console. Ten one-click presets with real request/response pairs. |
| Dossier builder. Download Markdown or JSON, or preview it. |
| Configuration and method: adapters, sources, limits, security model. |
| Public read-only dossier, reachable only with an unguessable issued token. |
API routes
Route | Methods | Purpose |
|
| Real store round trip; fails if a serverless runtime resolved to embedded storage. |
|
| Live normalised facts for a mint, with per-source attribution. |
|
| Stateless engine run. |
|
| List (filter/sort/page) and register. |
|
| Read, record a verdict, re-assay, retire. |
|
| Re-run the engine and persist the result. |
|
| Replay one chain, or every visible chain. |
|
| Markdown or JSON dossier as a download. |
|
| JSON-RPC 2.0 endpoint. |
Modules
File | Responsibility |
| Every normalized domain type. No third-party shape leaks past it. |
| The engine. The only place a score is computed. |
| Lexical comparator and identity normalization. |
| Browser-only on-device embeddings. |
| Canonical JSON and SHA-384. |
| Seal rule, genesis, replay. |
| Base58, ed25519 curve check, program-derived addresses. |
| Metaplex |
| Three key-free sources with timeout, retry and attribution. |
| Storage adapters and the production guard. |
| The service layer every write goes through. |
| Composes live facts + corpus + engine. |
| Tool definitions and JSON-RPC dispatch. |
Architecture
graph LR
Browser["Browser"] -->|paste mint| Press["Assay press"]
Press -->|POST /api/assay| Route["Next.js route handlers"]
Route --> Service["assay-service"]
Service --> Engine["runAssay · pure"]
Service --> Repo["repository service"]
Route --> Repo
Repo --> Neon[("Neon Postgres")]
Repo -.->|"no DATABASE_URL"| PGlite[("Embedded PGlite")]
Press -.->|"embeddings · no network"| Model["all-MiniLM-L6-v2<br/>ONNX in tab"]
Engine --> Score["versioned result"]
Score --> Press
classDef live fill:#22d3ee,stroke:#0e7490,color:#042f2e
classDef eng fill:#a78bfa,stroke:#6d28d9,color:#2e1065
classDef store fill:#94a3b8,stroke:#475569,color:#0f172a
classDef agent fill:#34d399,stroke:#047857,color:#022c22
class Model,Press live
class Engine,Score eng
class Neon,PGlite,Repo store
class Route agentServer components are the default. Client components exist only for interaction, browser APIs or the on-device model, which is imported dynamically inside an event handler and never enters the initial bundle.
The engine
Provenance integrity is a weighted sum of six factors. Higher means the identity stands up better.
graph TB
Facts["Live facts"] --> C1["metadata_collision 0.34"]
Corpus["Registry corpus"] --> C1
Claims["Existing claims"] --> C1
Facts --> C2["market_depth 0.18"]
Facts --> C3["holder_concentration 0.16"]
Facts --> C4["market_age 0.10"]
Facts --> C5["metadata_completeness 0.12"]
Claims --> C6["claim_evidence 0.10"]
C1 --> Sum["Σ weight × value × 100"]
C2 --> Sum
C3 --> Sum
C4 --> Sum
C5 --> Sum
C6 --> Sum
Sum --> Band["verdict band"]
Band --> Out["score · factors · evidence<br/>recommendation · degraded flag"]
classDef eng fill:#a78bfa,stroke:#6d28d9,color:#2e1065
classDef live fill:#22d3ee,stroke:#0e7490,color:#042f2e
classDef out fill:#34d399,stroke:#047857,color:#022c22
class Facts,Corpus,Claims,C1,C2,C3,C4,C5,C6 live
class Sum,Band eng
class Out outBand | Score | Meaning |
| 75–100 | No meaningful collision, real depth, inspectable identity. |
| 55–74 | No collision, but the evidence is incomplete. |
| 35–54 | Identity text resembles a registered origin. |
| 0–34 | Strong collision, thin independent evidence. |
A factor that cannot be measured is marked unavailable, contributes zero and
sets degraded: true. The remaining weights are not renormalised — a missing
holder reading must lower confidence, not inflate the score.
Data provenance
graph LR
RPC["Solana RPC<br/>no key"] --> Norm["normalize in<br/>src/lib/types.ts"]
Dex["DexScreener<br/>no key"] --> Norm
Jup["Jupiter price API<br/>no key"] --> Norm
Norm --> Sources["per-source attribution<br/>status · fetchedAt · endpoint"]
Norm --> Engine["engine"]
Alt{"all sources<br/>down?"}
Alt -->|no| Sources
Alt -->|yes| Sample["sealed sample<br/>status: fallback"]
Sample --> Sources
Store[("User claims")] -.->|"never merged or replaced"| Sample
classDef live fill:#22d3ee,stroke:#0e7490,color:#042f2e
classDef warn fill:#fbbf24,stroke:#b45309,color:#451a03
classDef store fill:#94a3b8,stroke:#475569,color:#0f172a
class RPC,Dex,Jup,Norm,Sources live
class Sample,Alt warn
class Store storeThree key-free sources: the Solana public RPC for on-chain metadata, supply and holder spread; DexScreener for pairs, liquidity, volume and creation time; the Jupiter price API as an independent second price reading, so a divergence between two sources is visible rather than averaged away.
Every source carries status, fetchedAt and its endpoint. When all of them are
unreachable, mints with a sealed sample return it flagged fallback with the
capture timestamp — never as current data, and never merged into a stored claim.
getTokenLargestAccounts is rate limited on the public RPC, so holder
concentration is explicitly state: "unavailable" with a reason when it cannot
be read. It is not rendered as zero.
Integrity
Each record has its own append-only chain.
graph LR
G["genesis<br/>SHA-384 of a fixed salt"] --> E1["event 1<br/>origin.registered"]
E1 -->|"prevSeal"| E2["event 2<br/>verdict"]
E2 -->|"prevSeal"| E3["event 3<br/>origin.assayed"]
E3 -->|"prevSeal"| E4["event 4<br/>origin.retired"]
E4 --> Head["head seal"]
Chain["/api/integrity/replay"] --> Recompute["recompute every seal"]
Recompute --> Result["ok, or the first<br/>broken sequence number"]
Tomb["tombstone retained<br/>on delete"] -.->|keeps replay possible| E4
classDef ok fill:#34d399,stroke:#047857,color:#022c22
classDef risk fill:#fb7185,stroke:#be123c,color:#4c0519
classDef store fill:#94a3b8,stroke:#475569,color:#0f172a
class G,E1,E2,E3,E4,Head,Recompute,Result ok
class Chain,Result store
class Tomb riskgenesis = SHA-384(UTF-8("mintline/genesis/v1"))
seal(n) = SHA-384( UTF-8(seal(n-1)) || canonicalJson(event(n)) )Canonical JSON recursively sorts object keys, preserves array order, drops
undefined, normalises -0, serialises dates as ISO-8601 UTC and rejects
non-finite numbers. Because each seal commits to its predecessor, editing one
event invalidates every seal after it, and replay reports the first sequence
number that fails to reproduce.
Deletion is a soft tombstone: the row survives so its chain stays
replayable. tests/integrity.test.ts pins the genesis vector, so an accidental
change to the hashing rule fails CI instead of silently invalidating history.
Agent sequence
sequenceDiagram
participant C as Agent
participant M as /api/mcp
participant S as repository service
participant D as Postgres
C->>M: initialize
M-->>C: protocolVersion + tools
C->>M: tools/list
M-->>C: 10 typed schemas
C->>M: tools/call assay_mint
M-->>C: score · factors · comparator
C->>M: tools/call register_origin
M->>S: same call the UI makes
S->>D: INSERT origin + chain event 1
S-->>M: origin id + chain head
M-->>C: result + link to /registry/id
C->>M: tools/call verify_integrity
M->>S: read events
S-->>M: events
M-->>C: ok, N events, no broken linkThe mutating tools do not write SQL. They call the same repository functions
the UI forms call, which is why "the agent mutates through the same path as the
UI" is a property of the code rather than a claim about it.
User journey
graph LR
A["Land"] --> B["Assay a mint"]
B --> C["Register an origin"]
C --> D["Inspect the specimen"]
D --> E["Record a verdict"]
E --> F["Re-assay live"]
F --> G["Export the dossier"]
G --> H["Share or retire"]
B -.->|"on-device model"| I["Semantic neighbours"]
classDef live fill:#22d3ee,stroke:#0e7490,color:#042f2e
classDef ok fill:#34d399,stroke:#047857,color:#022c22
classDef risk fill:#fb7185,stroke:#be123c,color:#4c0519
classDef eng fill:#a78bfa,stroke:#6d28d9,color:#2e1065
class A,B,D,E,F,G live
class C,H ok
class I engSecurity model
Concern | Mitigation |
Ownership | Signed HTTP-only |
Injection | All SQL parameterized. No user input is ever interpolated. |
SSRF | Off-chain metadata fetched only from an allow-listed HTTPS host set, with a time budget. |
Leakage | Stable error codes; no stack traces, SQL or environment values in a response body. |
Tampering | Append-only SHA-384 chains with a replay endpoint. |
Abuse | Fixed-window anonymous write limits. |
Rate limiting is best-effort on serverless. The limiter is held in process memory, so on a distributed runtime it is per instance. It raises the cost of casual scripting; a hard global cap needs a hosted limiter. This is stated here rather than implied.
Testing
npm run typecheck # tsc --noEmit
npm run lint # eslint, React Compiler rules on
npm run test # 151 tests
npm run build # next build
node scripts/smoke.mjs # 91-check journey over real HTTPThe suite covers the engine (weights sum to 1, bands tile 0–100, determinism, degradation, NaN safety, degenerate inputs), canonical JSON and the seal chain (tampered payload, rewritten seal, removed event, foreign genesis, reordered storage), the on-chain maths (PDA derivation against addresses observed on Solana mainnet, base58 round trips, curve membership, Metaplex decoding), the repository (CRUD, session isolation, tombstones, idempotency, paging), and the MCP contract (JSON-RPC error codes, tool schemas, and that a mutation over RPC is visible through the REST API).
scripts/smoke.mjs boots a real production server and walks the entire loop:
create → read back → decide → engine → MCP mutation → replay → export → delete →
confirm the tombstone still replays.
🚀 Deployment
DATABASE_URL must be set. On Vercel, attach a Neon Postgres project and the
variable is injected automatically. SESSION_SECRET should be set so sessions
survive a redeploy. NEXT_PUBLIC_SITE_URL should be the real production alias.
The app is stateless apart from Postgres, so it scales horizontally. neon()
uses the pooled endpoint, which is what serverless wants.
🗺️ Roadmap
Now — shipped
On-chain Metaplex decode with no indexer key, so a claim is grounded in chain state
Two comparators: a deterministic server default and an on-device neural upgrade
Six-factor versioned engine that reports its own evidence and its own gaps
Per-record SHA-384 chain with replay that names the first broken link
Ten MCP tools where mutations share the UI's service layer
Downloadable dossier in Markdown and JSON
Next
Registry-wide collision search — find every pair of registered origins whose identity text overlaps, so a launchpad can audit its whole board at once instead of one mint at a time
Watcher for newly deployed mints — a
/watchroute that polls a mint's metadata for changes and appends a sealed event when the name, URI or update authority moves, so an impersonation attempt becomes visible as a dated diffSigned origin statements — let an issuer publish a detached attestation that a visitor can verify locally, so a claim is corroborated by something other than Mintline's own score
graph LR
A["Registry-wide<br/>collision search"] --> B["Watcher:<br/>sealed metadata diffs"]
B --> C["Signed origin<br/>statements"]
classDef next fill:#fbbf24,stroke:#b45309,color:#451a03
classDef eng fill:#a78bfa,stroke:#6d28d9,color:#2e1065
class A,B next
class C engLater
Editions — have any observer append a signed observation to an existing record's chain, so a registry can accumulate evidence without any single party controlling it
Portable chain export — emit a record's events as a standalone verifiable bundle, so a claim can leave this app and still be checked
Other chains — the address maths and the chain rule are already generic; an EVM implementation would prove the design is not Solana-shaped
graph TB
A["Third-party<br/>observations"] --> B["Portable<br/>bundles"]
B --> C["EVM<br/>adapter"]
classDef later fill:#fb7185,stroke:#be123c,color:#4c0519
classDef store fill:#94a3b8,stroke:#475569,color:#0f172a
class A,B,C laterThird-party attribution
Solana — RPC and on-chain state
DexScreener — market structure
Jupiter — independent price corroboration
Xenova/all-MiniLM-L6-v2— Apache-2.0 sentence embeddings, run in the visitor's browser via transformers.jsMetaplex — Token Metadata account layout
⚠️ Disclaimer
Mintline measures observable structure: name collisions, metadata completeness, market depth, holder concentration and trading age. It does not predict price, does not evaluate whether a token is a good investment, and is not financial advice. A high provenance score means an identity is not obviously impersonating another — it is not an endorsement of the project behind it. Verify anything that matters against the issuer directly.
🤝 Contributing
Issues and pull requests are welcome. Read CONTRIBUTING.md first — especially the eight rules that are not negotiable, such as "one engine" and "never present a missing reading as a zero". Security reports go through SECURITY.md, not a public issue.
📄 License
MIT © 2026 Aniruddha Adak
This server cannot be deployed
Maintenance
Related MCP Connectors
Issue & verify signed (ed25519), hash-chained, timestamped provenance receipts for agent actions.
Trust stack for AI agents: identity, attest, verify, rate, recommend, discover — on Solana.
Data lookups, on-chain facts and a non-custodial Solana swap for agents. Sourced and dated.
Privacy-first document verification anchored to the public ledger.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI agents to certify documents, prove agreements, and verify counterparty claims with on-chain receipts on Hedera Hashgraph. Provides tools for document certification, two-party attestation, and agent registration with zero-config setup.642 npmMIT
- FlicenseAqualityBmaintenanceUniversal work attestation for autonomous agents. Register any AI agent or machine with persistent cryptographic identity, attest completed work with tamper-evident on-chain records, and query trust scores. The reputation layer for the agent economy. 3 MCP tools over SSE. Settled on Solana.112-
- AlicenseAqualityBmaintenanceProvides cryptographic truth infrastructure for AI agents, enabling them to seal content with SHA-256 and Ed25519, verify receipts, anchor them to Bitcoin via OpenTimestamps, generate citations, and audit chains of receipts.537 npm1MIT
- AlicenseBqualityBmaintenanceEnables software agents to maintain a signed, evidence-aware local ledger of claims, with policy-gated proposals, conflict queries, and constitution-pinned access through an MCP stdio server.73Apache 2.0