OrdJS is a JavaScript library designed to provide a convenient interface for interacting with the Ordinals Recursive Endpoints. It offers a suite of methods to facilitate the retrieval of block information, inscription metadata, and content associated with satoshis. This library itself is inscribed and supports recursive importation by other inscriptions.
Note: This is a beta version and may contain bugs. Please use it with caution and consider contributing improvements if you encounter any issues.
Below is the version history of the OrdJS library, including the inscription IDs associated with each version and additional notes or highlights.
| Version | Inscription ID | Notes |
|---|---|---|
| 0.1.0 | 3280180e7872eaef3cae589f3122f2f9527d3c1c30445cb13fc6eef03435aa66i0 |
Initial beta release |
| 0.1.1 | f35346db0fc826e3270b984c8cc219114e24321916ae19eb75ee22c7e55c6a1ei0 |
Decode CBOR |
| 0.1.2 | 4123e324aa3508ae7021a43a1dfc2d9d83fc35029d092877c57234f729068526i0 |
Fix getInscriptionId |
To use OrdJS in your project, you can import it via CDN like so:
<script src="/content/<OrdJS Inscription ID>"></script>An example usage is provided in the repository under /src/content/example.html. This demonstrates how to set up and use the library in your applications.
Once OrdJS is included in your project, initialize it with:
const ord = new OrdJS('');You can then use ord within an asynchronous function to access the library's functionality:
async function main() {
try {
const inscriptionId = await ord.getInscriptionId();
console.log('Current InscriptionId', inscriptionId);
// Additional method calls can be made here...
} catch (error) {
console.error('Error:', error);
}
}This example outlines the basic structure for utilizing OrdJS within an asynchronous function, allowing for the execution of various methods provided by the library.
getSatInscriptions(sat, page, index)treats only'',null,undefinedandNaNas absent, soindex0selects/r/sat/<sat>/at/0andpage0lists the first page. Passing bothpageandindexrejects before any request, because they are separate endpoints.getSatLastInscriptionContent(sat)resolves tonullwhen the sat carries no inscription (/r/sat/<sat>/at/-1answers{"id": null}). Transport and server errors still throw.getInscriptionContent(id)builds its base64 payload in bounded slices, so large inscriptions no longer overflow the call stack.- Only decoded metadata loads a dependency.
getDecodedMetadatavalidates the hex first, then loads the inscribed CBOR decoder once, on demand, sharing that load across concurrent callers; a failed load can be retried. The Buffer polyfill inscription is no longer referenced at all: hex is decoded natively and rejected when malformed, and no method publishes aBufferglobal any more — a consumer that relied oninit()providing one must supply its own.CBORis still reachable, viagetDecodedMetadataor an explicitloadAndUseDependency(). init()is now a no-op that only setsisInitialized, andrequest()no longer calls it. The flag no longer implies that any dependency is loaded; do not branch on it to decide whetherCBORexists.- The pinned CBOR decoder still loses precision on 64-bit integers and collapses distinct map keys. Fixing that needs a separate audited decoder inscription.
Every route below was checked against mainnet ordinals.com before being wrapped.
| Method | Route | Notes |
|---|---|---|
getBlockhash(height?) |
/r/blockhash[/<height>] |
|
getBlockheight() |
/r/blockheight |
|
getBlocktime() |
/r/blocktime |
|
getBlockInfo(query) |
/r/blockinfo/<height|hash> |
latest is not accepted: ord answers 400. A block hash is an art seed, not secure randomness |
getInscription(id) |
/r/inscription/<id> |
type, length, delegate, sat, location; location is mutable |
getMetadata(id) |
/r/metadata/<id> |
hex CBOR |
getDecodedMetadata(id) |
/r/metadata/<id> |
decodes via the CBOR inscription, loaded lazily |
getChildren(id, page?) |
/r/children/<id>[/<page>] |
IDs only |
getChildrenInscriptions(id, page?) |
/r/children/<id>/inscriptions[/<page>] |
full details; paginates with page |
getParents(id, page?) |
/r/parents/<id>[/<page>] |
paginates with page_index, not page |
getSatInscriptions(sat, page?, index?) |
/r/sat/<sat>[/<page>][/at/<index>] |
page and index are mutually exclusive |
getSatLastInscription(sat) |
/r/sat/<sat>/at/-1 |
may answer {"id": null} |
getSatLastInscriptionContent(sat) |
above, then /content/<id> |
resolves null for an empty sat |
getInscriptionContent(id) |
/content/<id> |
follows a delegate; returns {mime, base64} |
getUndelegatedContent(id) |
/r/undelegated-content/<id> |
the inscription's own bytes, delegate not followed |
getSatInscriptionContent(sat, index?) |
/r/sat/<sat>/at/<index>/content |
one request instead of two; needs the sat index; defaults to -1 |
request(endpoint) remains available for any JSON route without a wrapper.
Every failing request throws OrdJS <status> <endpoint>: <ord's own message> with a
status property, for example
OrdJS 404 /r/sat/1/at/0/content: inscription on sat 1 not found. Branch on
error.status, not on message text. Note that a 404 from
getSatInscriptionContent means either an empty sat or a server running
without the sat index; ord's message distinguishes them, so it is preserved rather
than collapsed into "not found".
getDecodedMetadata loads the inscribed decoder lazily, once. The current one
(a9f6a9b0…308566i0, cbor-js) decodes some metadata incorrectly:
| Input | Inscribed decoder | Consequence |
|---|---|---|
9007199254740993 |
9007199254740992 |
integers above 2^53 silently round: nanosecond timestamps, 18-decimal token amounts, snowflake IDs |
map with key 1 and key "1" |
{"1": …} |
one entry silently disappears |
map with key __proto__ |
no own keys, prototype changed | the value stops being data |
| ~1 MiB CBOR text | RangeError |
large metadata cannot be decoded at all |
vendor/cbor2-decoder.js is the proposed replacement, built by
node scripts/build-decoder.mjs from cbor2@2.3.0 (MIT, notice retained in the
artifact). vendor/cbor2-decoder.json records its bytes and sha256, and the gate
verifies the file still matches them. It is not inscribed yet, so
inscription_id is null.
The library asks for its decoder at a fixed /content/<id> path — no configuration
hook is inscribed for this. To try the candidate, serve it at that path, which is
what scripts/decoder-smoke.mjs does.
That script decodes the vendored RFC 8949 Appendix A fixture in Chromium through the real recursive loader: 59 of 59 decodable vectors correct. Upstream Appendix A has 82 entries; 23 are diagnostic-only and carry no expected value, so they are not in the fixture and 59/59 is not full Appendix A conformance. The same run against the currently inscribed decoder reports 55/59 with eight problems, so the check discriminates rather than decorates.
More than the broken cases change shape. The decoder smoke prints this table on every run; these are all cases the current decoder handles acceptably, so they are migration cost rather than fixes:
| CBOR | Inscribed decoder | Candidate |
|---|---|---|
{a:1, b:2} string keys |
plain object | plain object — unchanged |
{1:2, 3:4} integer keys |
plain object, meta[1] works |
Map, meta[1] is undefined |
| tag 0 / tag 1 date | string / number | Date |
| tag 32 URI | string | URL (adds a trailing slash) |
| tags 23 / 24 | Uint8Array |
Tag wrapper |
| integers > 2^53 | rounded number | BigInt |
So: ordinary string-keyed metadata keeps working as meta.foo, but a consumer
using integer keys, tags, or raw byte strings must be updated. That is the real
cost of the swap, and it is why the decision is a migration rather than a drop-in.
test/ runs on Node's built-in runner with no dependencies:
node --test test/ordjs.test.mjsBefore inscribing, run the full gate. It runs the tests, minifies with a pinned
configuration, re-runs the tests against the minified artifact, enforces a byte
budget against the size of the live inscription, drives Chromium, Firefox and
WebKit through the real /content/<id> recursive load, checks the decoder
candidate against the RFC 8949 fixture and against its own manifest, computes the
commit/reveal fee from the serialised transaction, and prints the byte counts, hash
and dependency inscription IDs a release record needs:
bun install # playwright, dev-only
npx playwright install chromium firefox webkit
node scripts/gate.mjs # --no-browser drops every browser stage--no-browser skips all three engines and the decoder conformance stage, so a
run with that flag is not release evidence.
CI runs the same gate on every push and pull request.
The gate does not cover, and a release still requires: a real ord server with the sat index both enabled and disabled, and a wallet dry-run of the actual commit/reveal transaction. The fee stage serialises the envelope, tapscript and witness exactly, but it assumes a standard commit shape (1 P2TR input, 2 P2TR outputs) rather than your wallet's real input selection.
Tests, examples, tooling and this README are repository files only; inscribing the
library uses a minified src/content/OrdJS.js and nothing else.
Contributions to the OrdJS library are welcome. If you have suggestions for improvements or have identified bugs, please feel free to contribute. You can do so by creating issues or pull requests on the repository. Your input is valuable in enhancing the functionality and reliability of this library.
The OrdJS Library is open source, provided under the MIT license.