ARC-56 program hash + ABI signature registry

Look up a deployed Algorand app's compiled TEAL by SHA-256 hash and get back a real ARC-56 spec you can use to decode its calls - or look up a raw 4-byte ARC-4 method selector and get back the human-readable method signature it came from.

Why this exists

A wallet about to send an application call usually only has the app ID. It can fetch the app's compiled approval and clear programs from the network, but without a spec it has no idea what methods exist, what arguments they take, or how to render the call to a user instead of a wall of opaque bytes.

This registry closes that gap: every ARC-56 spec indexed by scholtz/ARC56Registry has its compiled approval and clear programs hashed with SHA-256, and each hash maps to a durable URL for a matching spec.

Folder layout

RegistryPath
Approval program hash - spec URLapproval-programs/<hash[:3]>/<hash>.txt
Approval program hash - full specapproval-programs/<hash[:3]>/<hash>.arc56.json
Clear-state program hash - spec URLclear-programs/<hash[:3]>/<hash>.txt
Clear-state program hash - full specclear-programs/<hash[:3]>/<hash>.arc56.json
ABI method selector - signatureabi-signatures/<selector[:2]>/<selector>.txt
ABI method selector - signature + appsabi-signatures/<selector[:2]>/<selector>.json

Splitting on the hash's first few hex characters keeps any one folder from holding thousands of files. Each program-hash .txt file contains exactly one line: a raw.githubusercontent.com URL pinned to the commit that last touched the matching ARC-56 spec, so it keeps resolving to the exact spec that produced this hash even after the source file is edited or replaced later. The .arc56.json file right next to it is a byte-for-byte copy of that same spec, so you can skip resolving the URL entirely and read the full spec straight from this registry. Each abi-signatures/ .txt file contains the plain-text ABI method signature itself, e.g. add(uint64,uint64)uint128, and its .json file holds that signature plus the sorted list of approval-program hashes of every indexed app known to expose it.

How to look up a hash

  1. Fetch the app's compiled programs, e.g. via algod's GET /v2/applications/{app-id}, and base64-decode params.approval-program and/or params.clear-state-program.
  2. Compute sha256(program_bytes), hex-encoded, lowercase.
  3. Fetch this hash's spec from this site - /approval-programs/<hash[:3]>/<hash>.arc56.json or /clear-programs/<hash[:3]>/<hash>.arc56.json - using a URL relative to wherever this page is hosted. A 404 just means this registry hasn't indexed a spec producing that hash yet, not an error.
  4. If found, it's the full ARC-56 JSON spec - use it directly to decode the app's methods, argument types, and state layout. (The .txt file at the same path instead holds a durable, commit-pinned URL to the spec's source location, if you want that instead of the copy.)

Example: program hash to spec

Python

import base64, hashlib, urllib.request, json

app_id = 123456789
info = json.load(urllib.request.urlopen(f"https://mainnet-api.4160.nodely.dev/v2/applications/{app_id}"))
approval = base64.b64decode(info["params"]["approval-program"])
digest = hashlib.sha256(approval).hexdigest()

lookup_url = f"https://scholtz.github.io/ARC56Registry/approval-programs/{digest[:3]}/{digest}.arc56.json"
try:
    spec = json.load(urllib.request.urlopen(lookup_url))
    print("Found spec for", spec["name"])
except urllib.error.HTTPError:
    print("No ARC-56 spec indexed for this program yet")

TypeScript (Node.js 18+, built-in fetch)

import { createHash } from "node:crypto";

const appId = 123456789;
const info = await (await fetch(`https://mainnet-api.4160.nodely.dev/v2/applications/${appId}`)).json();
const approval = Buffer.from(info.params["approval-program"], "base64");
const digest = createHash("sha256").update(approval).digest("hex");

const lookupUrl = `https://scholtz.github.io/ARC56Registry/approval-programs/${digest.slice(0, 3)}/${digest}.arc56.json`;
const res = await fetch(lookupUrl);
if (res.ok) {
  const spec = await res.json();
  console.log("Found spec for", spec.name);
} else {
  console.log("No ARC-56 spec indexed for this program yet");
}

C# (.NET 8+)

using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text.Json;

using var http = new HttpClient();
var appId = 123456789;
var info = await http.GetFromJsonAsync<JsonElement>($"https://mainnet-api.4160.nodely.dev/v2/applications/{appId}");
var approval = Convert.FromBase64String(info.GetProperty("params").GetProperty("approval-program").GetString()!);
var digest = Convert.ToHexStringLower(SHA256.HashData(approval));

var lookupUrl = $"https://scholtz.github.io/ARC56Registry/approval-programs/{digest[..3]}/{digest}.arc56.json";
var response = await http.GetAsync(lookupUrl);
if (response.IsSuccessStatusCode)
{
    var spec = await response.Content.ReadFromJsonAsync<JsonElement>();
    Console.WriteLine($"Found spec for {spec.GetProperty("name").GetString()}");
}
else
{
    Console.WriteLine("No ARC-56 spec indexed for this program yet");
}

How to look up a method selector

Every ARC-4/ARC-56 application call's first argument is a 4-byte method selector: the first 4 bytes of SHA-512/256 (not SHA-256) over the method's ABI signature string, name(argtype,argtype,...)returntype. If you've extracted that selector from a transaction but don't have the app's ARC-56 spec, this registry can still resolve it to a human-readable signature:

  1. Hex-encode the 4 selector bytes, lowercase, e.g. 8aa3b61f.
  2. Fetch /abi-signatures/<selector[:2]>/<selector>.json - relative to wherever this page is hosted. A 404 just means this registry hasn't indexed a method with that selector yet, not an error.
  3. If found, it's a JSON object {"abi": "<signature>", "apps": [...]} - abi is the plain-text ABI signature, e.g. add(uint64,uint64)uint128, and apps is the sorted list of approval-program hashes of every indexed app known to expose it.

Example: method selector to signature

Python

import urllib.request, json

selector = "8aa3b61f"  # first 4 bytes of an app call's method-call arg, hex-encoded
lookup_url = f"https://scholtz.github.io/ARC56Registry/abi-signatures/{selector[:2]}/{selector}.json"
try:
    entry = json.load(urllib.request.urlopen(lookup_url))
    print("Selector resolves to", entry["abi"])  # add(uint64,uint64)uint128
    print("Known apps:", entry["apps"])
except urllib.error.HTTPError:
    print("No ABI method indexed for this selector yet")

TypeScript (Node.js 18+, built-in fetch)

const selector = "8aa3b61f"; // first 4 bytes of an app call's method-call arg, hex-encoded
const lookupUrl = `https://scholtz.github.io/ARC56Registry/abi-signatures/${selector.slice(0, 2)}/${selector}.json`;
const res = await fetch(lookupUrl);
if (res.ok) {
  const entry = await res.json();
  console.log("Selector resolves to", entry.abi); // add(uint64,uint64)uint128
  console.log("Known apps:", entry.apps);
} else {
  console.log("No ABI method indexed for this selector yet");
}

C# (.NET 8+)

using System.Linq;
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient();
var selector = "8aa3b61f"; // first 4 bytes of an app call's method-call arg, hex-encoded
var lookupUrl = $"https://scholtz.github.io/ARC56Registry/abi-signatures/{selector[..2]}/{selector}.json";
var response = await http.GetAsync(lookupUrl);
if (response.IsSuccessStatusCode)
{
    var entry = await response.Content.ReadFromJsonAsync<JsonElement>();
    Console.WriteLine($"Selector resolves to {entry.GetProperty("abi").GetString()}"); // add(uint64,uint64)uint128
    var apps = entry.GetProperty("apps").EnumerateArray().Select(a => a.GetString());
    Console.WriteLine($"Known apps: {string.Join(", ", apps)}");
}
else
{
    Console.WriteLine("No ABI method indexed for this selector yet");
}

Self-hosted use: Docker image

Everything on this page is also published as a self-hosted webserver image, scholtz2/arc56-registry (an unprivileged, non-root nginx container listening on port 8080), tagged with today's UTC date and latest. Run it to get your own HTTP endpoint serving these tables, so lookups don't depend on this site being reachable at call time:

docker run -d --name arc56-registry -p 8080:8080 scholtz2/arc56-registry:latest
curl http://localhost:8080/approval-programs/<hash[:3]>/<hash>.arc56.json

Prefer plain files instead of an HTTP endpoint? docker cp still works, since it's a normal filesystem, not a data-only image - see the image's own README (also served at /README.md inside the running container) for that and full usage details, and docs/docker-hash-registry.md for how it's built and published.

Freshness

All three tables are regenerated daily by generate-hash-registry.yml and this site is redeployed right after by deploy-hash-registry-pages.yml. If two indexed specs compile to the same program bytes, the hash points at whichever spec file is larger (generally the more complete one) - see docs/hash-registry.md for the exact tie-breaking rule and the ABI signature registry's own rules.