Implementation
Core protocol guides and code examples for builders integrating Urma claims into clients and mirrors. Backend-specific guides are in the Backends section.
1. Install
The Urma Go library provides core types, encoding/decoding, and schema generation. Backend implementations are separate modules.
go get go.lumeweb.com/urma2. Encode a Claim
Construct a UrmaClaim, serialize it as JSON, and wrap it in the appropriate envelope. The codec handles size validation against the 8192-byte on-chain limit.
import "go.lumeweb.com/urma"
claim := urma.UrmaClaim{
Version: 0,
Location: "sia", // backend-specific identifier
SourceClaimID: decodeHex("a1b2c3d4e5f60102030405060708090a0b0c0d0e"),
DataKey: [32]byte{66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
LocationData: rootLocationData, // backend-specific opaque JSON
}
// Encode to JSON (validates size budget)
jsonBytes, err := urma.EncodeClaim(claim)
if err != nil {
log.Fatal(err)
}
// Wrap in unsigned envelope for global Urma
envelope := urma.EncodeUnsignedEnvelope(jsonBytes)
// Or wrap in signed envelope for creator-owned Urma
// signedEnv, err := urma.EncodeSignedEnvelope(jsonBytes, channelClaimID, privKey, txInput0Hash)3. Decode & Verify
On discovery, decode the envelope, parse the JSON payload, and run the validation pipeline. Verify sourceClaimId matches the expected value computed from the source claim.
// rawValue is the claim value bytes from getclaimsforname
claimJSON, version, channelClaimID, signature, err := urma.DecodeEnvelope(rawValue)
if err != nil {
// Invalid envelope: skip this claim
return
}
claim, err := urma.DecodeClaim(claimJSON)
if err != nil {
return
}
// Verify sourceClaimId matches expected value
if claim.SourceClaimID != expectedClaimID {
return // mismatched claim
}
// For signed envelopes, verify the channel signature
if version == urma.EnvelopeSigned {
if err := urma.VerifySignedEnvelope(rawValue, txInput0Hash, channelPubKey); err != nil {
return // invalid signature
}
}
// Fetch root locationData from storage backend
// Then follow the manifest page chain to collect all blobsinfoValidation Order
4. Extending: New Storage Backend
The protocol is storage-agnostic. To add a new backend, define the manifest types and page chain construction for that backend's storage model. Core types use opaque JSON for locationData, data, and next. The backend decides what goes inside them.
// 1. Choose a location identifier string
const LocationMyBackend = "mybackend"
// 2. Define the locationData schema for the root pointer
// This is opaque JSON: whatever your backend needs to find the first manifest page
type MyBackendLocationData struct {
// e.g. a CID, a URL, a slab slice, etc.
}
// 3. Define manifest page payload types
type MyBackendManifest struct {
// Blob entries, stream metadata, etc.
}
// 4. Implement page chain construction
// Pages link in reverse: last page first, first page last.
// Each page's `next` field references the previous page's storage location.infoNote
UrmaClaim, ManifestPage) remain unchanged. New backends only define their own locationData schema and manifest payload types. See the Sia backend for a complete implementation reference.