Interop spec v1

A small, strict JSON schema that describes any 3D asset inscribed on Bitcoin so it renders correctly across Bitverse, JUNGL, KENOBI hubs, and any other open metaverse that adopts it. This is a strict superset of avatar-interop.

Where it goes

Two ways to attach a metadata document to your asset inscription:

  1. Sibling text inscription, content-type application/json, body = the AssetMetadata JSON. The id of this sibling becomes the metadata_inscription_id stored in the registry.
  2. Bundled: a single tar of asset.glb + metadata.json with content-type application/bitverse-bundle+json.

Top-level shape

{
  "v": 1,
  "kind": "prop",
  "name": "Pixel Mushroom Guardian",
  "description": "A friendly mushroom NPC for cozy gardens.",
  "license": "CC0-1.0",
  "authorAddress": "bc1pae6v9gz...75ye5e",
  "authorHandle": "polygonal-mind",
  "url": "https://arweave.net/abc.../mushroom.glb",
  "thumbnailUrl": "https://arweave.net/abc.../mushroom.png",
  "scale": 1.0,
  "tags": ["pixel", "fantasy", "garden", "friendly"],
  "compat": { "minSdk": "1.0.0", "worksOn": ["bitverse", "jungl"] }
}

Required fields

FieldTypeNotes
vnumberSpec version. Today: 1.
kindstring enumOne of avatarpropimageaudiovideohtmlkenobiscene-kit
namestringDisplay name. Up to 80 chars.
licensestringSPDX license id. CC0-1.0 strongly recommended.
authorAddressstringAuthor wallet (BTC). Drives attribution + royalties.
url or inscriptionIdstringWhere the actual asset bytes live. At least one required.

Avatar (kind = "avatar")

{
  "v": 1, "kind": "avatar",
  "name": "Onchain Monkey #4288",
  "license": "CC0-1.0",
  "authorAddress": "bc1pae6...",
  "inscriptionId": "abc...i0",
  "avatar": {
    "vrmVersion": "1",
    "pose": "T",
    "baseScale": 1.0,
    "eyeHeightM": 1.6,
    "maxWalkSpeed": 5.5,
    "animations": {
      "idle": "ord://idle.glb",
      "walk": "ord://walk.glb"
    }
  }
}

The avatar block is a strict superset of avatar-interop so any VRM that already declares it is automatically compatible with Bitverse.

Media (kind = "audio" / "video")

{
  "v": 1, "kind": "audio",
  "name": "Lo-fi Bitmap Beat",
  "license": "CC-BY-4.0",
  "authorAddress": "bc1pae6...",
  "inscriptionId": "abc...i0",
  "media": {
    "loop": true,
    "volume": 0.8,
    "falloffFullM": 2,
    "falloffSilentM": 10,
    "interactive": true
  }
}

Falloff metres override the renderer defaults (full at 2m, silent at 10m for audio; full at 3m, silent at 12m for video). interactive = false will mute click-to-toggle for ambient sources you don't want guests muting.

HTML (kind = "html")

{
  "v": 1, "kind": "html",
  "name": "Mini Tetris Cabinet",
  "license": "MIT",
  "authorAddress": "bc1pae6...",
  "inscriptionId": "abc...i0",
  "html": {
    "capabilities": ["scene", "events", "broadcast"],
    "preferredSizeM": { "w": 2.4, "h": 1.6 },
    "facing": "billboard"
  }
}

HTML scenes load inside an iframe sandboxed with sandbox="allow-scripts" only โ€” no same-origin, no popups, no pointer lock. Capabilities listed here MUST be a subset of the sandbox host's allowed list. The visitor sees a one-time consent dialog before mounting any HTML scene that requests AI / persist / multiplayer permissions.

Scene Kit (kind = "scene-kit")

{
  "v": 1, "kind": "scene-kit",
  "name": "Cozy Cottage Starter",
  "license": "CC0-1.0",
  "authorAddress": "bc1pae6...",
  "url": "https://arweave.net/...kit.json",
  "sceneKit": {
    "items": [
      { "ref": "polygonal-mind/cottage",  "refKind": "cc0",         "pos": { "x": 0, "y": 0, "z": 0 } },
      { "ref": "abc...i0",                "refKind": "inscription", "pos": { "x": 2, "y": 0, "z": 1 } },
      { "ref": "polygonal-mind/lantern",  "refKind": "cc0",         "pos": { "x": 1, "y": 0, "z": 1 }, "scale": 0.5 }
    ]
  }
}

Scene kits are bundles a creator can publish once and visitors can drop with a single click. Children are placed relative to where the visitor confirmed the kit anchor.

Round-trip compatibility

The validator preserves any field it doesn't know about. If the spec adds a new section in v2, v1 hosts still display the asset using the v1 fields, and forwarding the metadata between platforms doesn't drop data.

TypeScript types

Plugin authors and tooling can import the typed schema directly:

import {
  parseAssetMetadata,
  defaultPropMetadata,
  type AssetMetadata,
} from "@bitverse/sdk/interop";

const meta = parseAssetMetadata(json);
if (!meta) throw new Error("metadata failed validation");
console.log(meta.kind, meta.name);