# Hashlock-Algo — Proof of Existence on Algorand > Cryptographic timestamping service. Proves that a document existed at a specific moment in time, recorded immutably on the Algorand blockchain via the x402 payment protocol. Hashlock-Algo lets any user or AI agent timestamp a document by sending its SHA-256 hash to the Algorand blockchain. Payment (0.005 USDC) is handled automatically via x402 — no account, no subscription. Verification is free and public forever. Two payment options are available: - Algorand USDC (via @x402-avm) — endpoint /timestamp - Base USDC / EVM (via @x402/evm, EIP-3009) — endpoint /timestamp-base ## Key facts - Network: Algorand Testnet (Mainnet service is temporarily suspended — testnet only for now) - Cost: 0.005 USDC per timestamp (either payment method) - Payment protocol: x402 v2 (HTTP 402 -> PAYMENT-SIGNATURE header) - Certificate: PDF delivered as base64 in the API response - File privacy: only the SHA-256 hash is ever sent — the file never leaves the client - Metadata privacy: by default, only the hash and timestamp are written on-chain. Filename, description and owner address stay server-side (erasable on request). Pass publicMetadata: true to publish them permanently in the transaction note. ## Networks ### Testnet - Base URL: https://hashlock.pronodealgo.xyz/api - Algorand USDC ASA: 10458941 - Base USDC (EVM): 0x036CbD53842c5426634e7929541eC2318f3dCF7e (Base Sepolia, eip155:84532) - Explorer: https://lora.algokit.io/testnet - Frontend: https://hashlock.pronodealgo.xyz/ ### Mainnet Temporarily suspended. The former mainnet endpoints return HTTP 410. This document will be updated when the mainnet service reopens. ## API endpoints ### POST /timestamp Timestamp a document hash. Payment via Algorand USDC (x402-avm). Request body (JSON): hash string required SHA-256 hex hash of the document (64 chars) filename string optional Original file name (max 120 chars, no control chars) filesize number optional File size in bytes ownerAddress string optional Algorand address of the owner (58 chars base32) ownerSignature string optional Base64 ed25519 signature (64 bytes) of the message "hashlock-algo-v1:timestamp:" signed by ownerAddress. Proves ownership: the server then writes a salted commitment SHA256(address:salt) on-chain (never the address itself) and returns ownerSalt ONCE — store it, it is required to later prove the link address<->timestamp. Algorand addresses only. description string optional Short label. Max 100 chars. a-z, A-Z, 0-9, spaces only. publicMetadata boolean optional Default false. If true, filename/filesize/description and ownerAddress are embedded in the PUBLIC, PERMANENT transaction note. If false they stay server-side only. blinded boolean optional Declare that the hash is SHA256(file + secret) computed client-side. The secret never reaches the server; the flag is recorded so verifiers know a secret is required. source string optional "agent" to identify AI agent calls (default: "web") Flow: 1. POST without payment -> server replies 402 with PAYMENT-REQUIRED header (base64-JSON) 2. Build and sign Algorand USDC transfer 3. Retry POST with PAYMENT-SIGNATURE header (base64-JSON) Response 200: { txId, block, createdAt, explorerUrl, certificate (base64 PDF), blinded, ownerVerified, ownerSalt (only when ownerSignature was provided and valid) } Response 409 (already timestamped): { existing: { txId, block, createdAt, explorerUrl }, certificate (base64 PDF) } ### POST /timestamp-base Timestamp a document hash. Payment via Base USDC (EVM, EIP-3009 / x402). Request body (JSON): same fields as /timestamp. ownerAddress may be an EVM address (0x + 40 hex) or an Algorand address, but ownerSignature (proof of ownership) is only available for Algorand addresses. Flow: 1. POST without payment -> server replies 402 with PAYMENT-REQUIRED header (base64-JSON) The header contains: scheme "exact", network "eip155:84532" (testnet), amount "5000" (= 0.005 USDC, 6 decimals), asset (USDC contract), payTo (service wallet) 2. Sign EIP-3009 TransferWithAuthorization off-chain (no gas needed) 3. Retry POST with PAYMENT-SIGNATURE header containing the signed authorization 4. The x402 facilitator settles the USDC transfer on Base; Algorand inscription is done server-side Response 200: { txId, block, createdAt, explorerUrl, certificate (base64 PDF), blinded, ownerVerified, ownerSalt } (txId and explorerUrl are Algorand — the inscription is always on Algorand) Response 409: same as /timestamp ### GET /verify/:hash Public, free. Check if a SHA-256 hash has been timestamped. Testnet: https://hashlock.pronodealgo.xyz/api/verify/:hash Response: { verified: bool, hash, txId, block, createdAt, explorerUrl, blinded, filename (null unless the owner opted into publicMetadata) } ### GET /history/:address Returns the last timestamps for a given owner address (Algorand or EVM). Testnet: https://hashlock.pronodealgo.xyz/api/history/:address Query parameters: limit number optional Max records to return (default: 20, max: 200) offset number optional Pagination offset (default: 0) Response: { address, total, count, limit, offset, records: [{ hash, txId, block, network, filename, filesize, description, createdAt, explorerUrl }] } filename and description are null unless the owner opted into publicMetadata. ### GET /stats Returns global service statistics. Response includes: total, today, wallets, totalWeb, totalAgent, totalAgentAlgo, totalAgentEvm, dailyLast30, dailyLast30Web, dailyLast30Agent, dailyLast30AgentAlgo, dailyLast30AgentEvm, network, walletAddress, usdcAssetId, explorerUrl ## Node.js integration — Algorand USDC (AI agents) Install: npm install @x402-avm/fetch @x402-avm/avm algosdk Environment (.env): AVM_PRIVATE_KEY= Code (Testnet): import { wrapFetchWithPayment, x402Client } from '@x402-avm/fetch' import { ExactAvmScheme, toClientAvmSigner, ALGORAND_TESTNET_CAIP2 } from '@x402-avm/avm' import { createHash } from 'crypto' import { readFileSync } from 'fs' const signer = toClientAvmSigner(process.env.AVM_PRIVATE_KEY) const scheme = new ExactAvmScheme(signer) const client = new x402Client() client.register(ALGORAND_TESTNET_CAIP2, scheme) const fetchWithPayment = wrapFetchWithPayment(fetch, client) const hash = createHash('sha256').update(readFileSync('./doc.pdf')).digest('hex') const res = await fetchWithPayment( 'https://hashlock.pronodealgo.xyz/api/timestamp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hash, filename: 'doc.pdf', source: 'agent' }) } ) const { txId, explorerUrl, certificate } = await res.json() Prerequisites: - Algorand wallet with USDC (opted in to the ASA) and ~0.001 ALGO for fees - Use algosdk to generate: node -e "import('algosdk').then(m=>{const a=m.default.generateAccount();console.log('AVM_PRIVATE_KEY='+Buffer.from(a.sk).toString('base64'))})" ## Node.js integration — Base USDC / EVM (AI agents) Install: npm install viem Environment (.env): EVM_PRIVATE_KEY= The server returns the correct chain ID and USDC contract address in the 402 response, so the signing logic adapts automatically. Code (Testnet — Base Sepolia): API URL: https://hashlock.pronodealgo.xyz/api/timestamp-base Chain: eip155:84532 | USDC: 0x036CbD53842c5426634e7929541eC2318f3dCF7e import { privateKeyToAccount } from 'viem/accounts' import { getAddress } from 'viem' import { createHash, randomBytes } from 'crypto' import { readFileSync } from 'fs' const API_URL = 'https://hashlock.pronodealgo.xyz/api/timestamp-base' const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY.startsWith('0x') ? process.env.EVM_PRIVATE_KEY : `0x${process.env.EVM_PRIVATE_KEY}`) const hash = createHash('sha256').update(readFileSync('./doc.pdf')).digest('hex') const body = JSON.stringify({ hash, filename: 'doc.pdf', ownerAddress: account.address, source: 'agent' }) // Step 1: get payment requirements (chain ID and USDC address are returned by the server) const res1 = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body }) // res1.status === 402 const req = JSON.parse(Buffer.from(res1.headers.get('PAYMENT-REQUIRED'), 'base64').toString()).accepts[0] // Step 2: sign EIP-3009 TransferWithAuthorization (gasless, off-chain) const now = Math.floor(Date.now() / 1000) const nonce = `0x${randomBytes(32).toString('hex')}` const chainId = parseInt(req.network.split(':')[1]) // 84532 testnet const signature = await account.signTypedData({ domain: { name: req.extra.name, version: req.extra.version, chainId, verifyingContract: getAddress(req.asset) }, types: { TransferWithAuthorization: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' } ]}, primaryType: 'TransferWithAuthorization', message: { from: account.address, to: getAddress(req.payTo), value: BigInt(req.amount), validAfter: BigInt(now - 600), validBefore: BigInt(now + req.maxTimeoutSeconds), nonce } }) // Step 3: retry with payment signature const paymentPayload = { x402Version: 2, payload: { authorization: { from: account.address, to: getAddress(req.payTo), value: req.amount, validAfter: String(now - 600), validBefore: String(now + req.maxTimeoutSeconds), nonce }, signature }, accepted: req } const res2 = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(paymentPayload)).toString('base64') }, body }) const { txId, explorerUrl, certificate } = await res2.json() Prerequisites (Testnet): - EVM wallet with USDC on Base Sepolia - Free testnet USDC: https://faucet.circle.com — select "Base Sepolia" - Free testnet ETH: https://www.alchemy.com/faucets/base-sepolia ## Links ### Testnet - Frontend: https://hashlock.pronodealgo.xyz/ - API base URL: https://hashlock.pronodealgo.xyz/api - Verify: https://hashlock.pronodealgo.xyz/api/verify/:hash - History: https://hashlock.pronodealgo.xyz/api/history/:address - Stats: https://hashlock.pronodealgo.xyz/api/stats - Algorand explorer: https://lora.algokit.io/testnet - Base Sepolia explorer: https://sepolia.basescan.org - Free USDC faucet (Base Sepolia): https://faucet.circle.com — select "Base Sepolia" - Free ALGO+USDC faucet (Algorand testnet): https://dispenser.testnet.aws.algodev.network ### Mainnet Temporarily suspended — will reopen at https://hashlock.pronodealgo.xyz when ready. ### Libraries - x402-avm (Algorand) on npm: https://www.npmjs.com/package/@x402-avm/fetch - x402 EVM on npm: https://www.npmjs.com/package/@x402/express - viem on npm: https://www.npmjs.com/package/viem