Quick Start
A key, a hash, and about twenty minutes — most of it waiting for the first batch to close. You can follow along with the SDK or with plain curl.
Before you start#
You need Node.js 18+ for the SDK, or just curl. The SDK is not on npm: install a pinned release from Git.
npm install github:TLAY-IO/hashanchor#v1.0.1Get a free API key
The key is self-serve; there is no form to fill in.
import { HashAnchor } from "@tlay/hashanchor-client";
const { apiKey, plan, quota } = await HashAnchor.provision("my-app");curl -X POST https://hashanchor.xid.network/auth/provision \
-H "Content-Type: application/json" \
-d '{"name":"my-app"}'{ "tenantId": "…", "apiKey": "ha_…", "plan": "free",
"quota": { "limit": 100, "remaining": 100 } }Keep it in an environment variable. Do not commit it, and do not flash it into firmware.
Submit a record
Hash the record yourself and only 32 bytes leave your system — or hand over the content and let the service hash it.
const client = new HashAnchor({ apiKey: process.env.HASHANCHOR_API_KEY });
// a hash you computed yourself …
const { hash } = await client.submitHash("0x" + "ab".repeat(32));
// … or content for the service to hash
await client.anchor("my first anchored record");{ "status": "accepted", "id": "…", "hash": "0x…" }Keep the hash — it is how you look the record up. accepted (or duplicate) means queued, not anchored.
Wait for the batch
Your hash joins the next Merkle batch, the batch's root is anchored in one transaction, and only then does a receipt exist. On the managed deployment a batch closes every 15 minutes.
anchored in 15–20 minutes. Until then the receipt endpoint answers 404 — the correct answer to "is it anchored yet?" asked too early.async function waitUntilAnchored(client, hash) {
const deadline = Date.now() + 30 * 60_000;
let delay = 5_000;
while (Date.now() < deadline) {
const { status } = await client.getStatus(hash);
if (status === "anchored") return client.getReceipt(hash);
if (status === "failed") throw new Error("anchoring failed");
await new Promise((r) => setTimeout(r, delay));
delay = Math.min(delay * 2, 60_000);
}
throw new Error("timed out");
}For a long-running application, record the hash and sweep later with batchStatus(hashes) instead of holding a request open.
Get the receipt and check it offline
import { verifyReceiptProof } from "@tlay/hashanchor-client";
const receipt = await client.getReceipt(hash);
verifyReceiptProof(receipt); // true — no network involved{ "@context": "https://hashanchor.io/receipt/v1",
"hash": "0x…", "merkleRoot": "0x…",
"proof": [ { "position": "right", "data": "0x…" }, … ],
"anchor": { "chainId": 137, "contractAddress": "0x…",
"txHash": "0x…", "blockNumber": …, "blockTimestamp": … } }The proof walks from your hash, pair by pair, to merkleRoot. You can also try this on the HashAnchor page, which runs the same check in your browser.
Check the anchor yourself
The offline check cannot see the chain. Ask the service — client.verify(hash), no key needed — or, for assurance that trusts no one, open anchor.txHash on the explorer for anchor.chainId and confirm the transaction committed the receipt's merkleRoot.
chainId | Explorer |
|---|---|
| 137 | polygonscan.com |
| 8453 | basescan.org |
| 56 | bscscan.com |
Ask getChains() for the chains the deployment anchors to today rather than hardcoding them.
Your record now has a receipt that anyone can verify without your database, and without trusting TLAY. Store it with whoever may need to check it later.