Contracts
ThesisLock is five Clarity 3 contracts deployed to Stacks mainnet under the same principal. Each one stores anchors in a different shape; all of them are public and readable without a wallet.
Deployer
Every contract lives at the deployer principal SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM. A contract identifier is the principal followed by the contract name, for example SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM.thesislock.
The five contracts
| Contract | Purpose |
|---|---|
thesislock | Original single-hash anchor. Stores one immutable record per hash: who anchored it, the Stacks and burn block heights, and an optional label. |
thesislock-batch | Anchors up to ten hashes in one transaction, keyed by hash and owner. Duplicate hashes for the same owner are silently skipped. |
thesislock-registry | Per-principal append-only index of anchors. Powers the My Anchors page and recent-activity reads. |
thesislock-proof | SIP-009 NFT issuing soulbound proof tokens. Minting anchors a hash and mints a non-transferable token; transfers always fail. |
thesislock-groups | Named groups for collaborative anchoring. An admin manages members; any member can append to a shared, on-chain history. |
Function signatures
Public functions write state and must be signed by a wallet. Read-only functions take no gas and can be called against the Hiro API directly (see Direct API reads below).
thesislock
;; public
(anchor-document (hash (buff 32)) (label (string-ascii 64)))
;; read-only
(get-anchor (hash (buff 32))) ;; (optional { anchored-by, stacks-block, burn-block, label })
(is-anchored (hash (buff 32))) ;; boolthesislock-batch
;; public
(anchor-batch (entries (list 10 { hash: (buff 32), label: (string-ascii 64) })))
;; read-only
(get-batch-anchor (hash (buff 32)) (owner principal)) ;; (optional { label, stacks-block, burn-block, batch-id })
(get-batch-count) ;; uintthesislock-registry
;; public
(register-anchor (hash (buff 32)) (label (string-ascii 64)))
;; read-only
(get-anchor-count (owner principal)) ;; uint
(get-anchor-at (owner principal) (index uint)) ;; (optional { hash, label, stacks-block })
(get-recent-anchors (owner principal)) ;; (list ...) newest firstthesislock-proof
;; public
(mint-proof (hash (buff 32)) (label (string-ascii 64)))
(transfer (token-id uint) (sender principal) (recipient principal)) ;; always (err u401)
;; read-only (SIP-009)
(get-last-token-id) ;; (ok uint)
(get-token-uri (token-id uint)) ;; (ok (optional ...))
(get-owner (token-id uint)) ;; (ok (optional principal))
(get-proof (token-id uint)) ;; (optional { hash, label, stacks-block })
(get-token-id-by-hash (hash (buff 32))) ;; (optional uint)
(get-proof-by-hash (hash (buff 32))) ;; (optional { ... })thesislock-groups
;; public
(create-group (name (string-ascii 64)))
(add-member (group-id uint) (member principal))
(remove-member (group-id uint) (member principal))
(anchor-to-group (group-id uint) (hash (buff 32)) (label (string-ascii 64)))
;; read-only
(get-group (group-id uint))
(is-member (group-id uint) (who principal)) ;; bool
(get-group-anchor-count (group-id uint)) ;; uint
(get-group-anchor-at (group-id uint) (index uint))
(get-recent-group-anchors (group-id uint))Non-admin calls to add-member or remove-member fail with u403. A duplicate proof hash fails with u409.
Direct API reads
Read-only functions can be called over HTTP against the public Hiro mainnet API at https://api.mainnet.hiro.so. Arguments are serialized Clarity values. A 32-byte hash is encoded as a (buff 32) by prefixing the hex with 0x0200000020 (type byte 02, big-endian length 00000020).
is-anchored: a boolean check
The quickest lookup. Returns 0x03 for true and 0x04 for false.
HASH=0000000000000000000000000000000000000000000000000000000000000000
curl -sX POST \
https://api.mainnet.hiro.so/v2/contracts/call-read/SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM/thesislock/is-anchored \
-H 'Content-Type: application/json' \
--data "{\"sender\":\"SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM\",\"arguments\":[\"0x0200000020${HASH}\"]}"get-anchor: the full record
Returns a serialized Clarity optional. 0x09 means none (never anchored); a payload beginning 0x0a0c is (some (tuple ...)).
curl -sX POST \
https://api.mainnet.hiro.so/v2/contracts/call-read/SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM/thesislock/get-anchor \
-H 'Content-Type: application/json' \
--data "{\"sender\":\"SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM\",\"arguments\":[\"0x0200000020${HASH}\"]}"Decode a some payload into JSON with @stacks/transactions:
node -e '
const { deserializeCV, cvToJSON } = require("@stacks/transactions");
const hex = "0a0c0000000..."; // strip 0x, paste from API response
console.log(JSON.stringify(cvToJSON(deserializeCV(hex)), null, 2));
'get-batch-anchor: serialize the owner too
Batch anchors are keyed by both hash and owner, so the principal is a second serialized argument. Let @stacks/transactions encode it:
HASH=0000000000000000000000000000000000000000000000000000000000000000
OWNER=SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM
OWNER_HEX=$(node -e '
const { principalCV, serializeCV } = require("@stacks/transactions");
process.stdout.write("0x" + serializeCV(principalCV(process.argv[1])));
' "$OWNER")
curl -sX POST \
https://api.mainnet.hiro.so/v2/contracts/call-read/SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM/thesislock-batch/get-batch-anchor \
-H 'Content-Type: application/json' \
--data "{\"sender\":\"${OWNER}\",\"arguments\":[\"0x0200000020${HASH}\",\"${OWNER_HEX}\"]}"get-anchor-count: a single principal argument
OWNER=SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM
OWNER_HEX=$(node -e '
const { principalCV, serializeCV } = require("@stacks/transactions");
process.stdout.write("0x" + serializeCV(principalCV(process.argv[1])));
' "$OWNER")
curl -sX POST \
https://api.mainnet.hiro.so/v2/contracts/call-read/SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM/thesislock-registry/get-anchor-count \
-H 'Content-Type: application/json' \
--data "{\"sender\":\"${OWNER}\",\"arguments\":[\"${OWNER_HEX}\"]}"Prefer not to serialize by hand?
- The REST API wraps these reads and returns plain JSON, no Clarity encoding required.
- The TypeScript SDK handles serialization and decoding for you.
- The CLI verifies from the terminal and exits non-zero on a miss.