Skip to content

Repository files navigation

Adinals

Live application · GitHub · MIT License

Adinals is an open-source protocol explorer and BRC-100 wallet application for finite, ownable live-content slots on Bitcoin SV. Creators define a collection, mint its slots, and choose whether owner updates publish automatically or require creator review. Owners can update, list, buy, and resell a slot without giving Adinals their private keys.

The production application uses the exact MAP identity app=adinals, type=ord, and protocolVersion=3.

Beta: Mainnet actions use real BSV and create permanent public records. Protocol-valid content is not automatically safe, lawful, endorsed, or guaranteed display. Publishers retain final control.

What is included

  • Public collection discovery and immutable collection/ad routes.
  • Text and image collections with finite capacities and permanent rules.
  • BRC-100 collection creation, self-minting, owner updates, creator decisions, OrdLock listings, cancellation, and purchases.
  • Independent browser verification of MAP, SIGMA, raw transactions, ownership transitions, owner epochs, duplicate slots, expiration, and collection rules.
  • JSON collection/ad endpoints and a framework-neutral embed component.
  • Recovery-aware no-send construction that avoids blindly retrying uncertain wallet actions.

Wallet model

Adinals talks to wallets through the standard WalletInterface from @bsv/sdk. Yours Wallet and Metanet Desktop receive the same BRC-100 calls and the same transaction bytes; the application does not branch on wallet brand.

Production first negotiates the BRC-99 basket p 1sat ordinals, then falls back to the portable adinals basket when a wallet does not implement that scheme. Baskets track outputs; they do not determine private keys or recipient addresses. Funding, key derivation, signatures, and custody remain inside the connected wallet.

Version 3 in brief

  1. A creator-signed collection fixes capacity, format, approval mode, content policy, optional expiration, and creative limits.
  2. The creator signs one numbered collectionItem for each finite slot.
  3. The slot origin remains its permanent identity while its one-satoshi output advances through updates, transfers, listings, and purchases.
  4. An adUpdate spends the exact current state at input 0, returns the state at output 0, and commits the complete creative record at output 1.
  5. Creator-reviewed collections require a creator-signed adDecision tied to the exact update transition and owner epoch. Open collections do not.
  6. Readers independently reconstruct custody and eligibility; publishers still choose what to display.

The exact record subtypes are collection, collectionItem, adUpdate, and adDecision. Version 2 records remain historical and are not silently treated as version 3.

Run locally

Requirements: a current Node.js release supported by Vite 8 and a compatible BRC-100 wallet.

npm install
npm run dev

The development server listens on port 5176.

npm test
npm run build
npm run preview

The repository currently passes 287 browser and application tests across 51 Node test files, 85 backend overlay tests, the independent collection script/fixture verifier, TypeScript compilation, and the production Vite build.

Environment switches

Production is the default. These variables are explicit operator controls:

Variable Purpose
VITE_ADINALS_ENV=development Use the isolated development namespace.
VITE_ENABLE_COLLECTION_PUBLISH=false Emergency read-only switch for collection creation.
VITE_ENABLE_LIFECYCLE_PUBLISH=false Emergency read-only switch for mint/update/market actions.
VITE_ADINALS_API_BASE Override the derived JSON reader base URL.
VITE_ADINALS_EMBED_SCRIPT_URL Override the hosted web-component URL.
VITE_ADINALS_OVERLAY_URL Overlay endpoint for browser delivery. Committed in .env.production for hosted builds; a local .env overrides it, and development falls back to http://localhost:8080.

Do not change the production namespace, key protocol, basket, or action labels casually; existing wallet custody and public records depend on them.

Public reading and embedding

The hosted reader currently uses this base URL:

https://tenmillionfoxes-99288f417d7b.herokuapp.com/adinals/v1

It exposes:

  • https://tenmillionfoxes-99288f417d7b.herokuapp.com/adinals/v1/collections/{origin}/live
  • https://tenmillionfoxes-99288f417d7b.herokuapp.com/adinals/v1/ads/{origin}

Those are reader-service routes, not relative routes on adinals.com. The web application and embed component use the configured VITE_ADINALS_API_BASE. A stable api.adinals.com origin is planned before publishing the agent SDK.

Consumers must check displayEligible. Render text as text rather than HTML, treat destination URLs as untrusted external links, and keep a local fallback for temporary reader outages. The supplied public/adinals-embed.js component supports both text and image creatives.

Current infrastructure boundary

Reads come from the overlay: it discovers the collections, assembles every collection and ad from verified evidence, and serves the image creatives. When it answers incompletely the application falls back as a whole to GorillaPool for discovery and spend history, WhatsOnChain for raw transactions, and the public content hosts for images. Falling back covers an empty answer as well as an error, because an overlay only knows what was submitted or backfilled into it. A successful index submission does not prove an output is queryable, so the UI verifies exact outpoints separately and labels incomplete states as provisional rather than guessing.

Falling back also covers a complete answer the chain has moved past. A node that never received a record — a purchase made by someone else's wallet is the usual case — answers confidently with a chain that ends before it, so each load asks the public indexer whether the rendered state has a successor, or a block the node does not know about. Either one is proof, and only those two: a record the overlay holds and the indexer has not seen yet is what publishing looks like, and must never cause a fallback. When it does fire, the view comes from the public reader and the wallet immediately offers the node what it is missing.

Writes additionally go to an Adinals overlay. Every wallet-accepted collection and lifecycle action queues its verified Atomic BEEF, treats submit as a processing acknowledgment, polls the exact hydrated outputs, persists provisional, indexed, retrying, and failed delivery state, and retries outages without ever changing the wallet action's own success. An update requires both its state output and its record output before it counts as delivered.

A connected wallet also teaches the node what it holds. After a wallet connects, Adinals walks each Adinals basket output back through the transactions its own BEEF carries, asks the overlay which it already has, and offers the rest. Those records are already public on chain; nothing is created, signed, or broadcast. It runs at most once a day per wallet and endpoint, in the background, and the developer panel exposes the same thing as separate inspect and submit controls. Records whose lineage the wallet cannot complete are reported rather than sent, because a later state only admits once the overlay holds the output it spent. This is the one ingestion path that involves no third party.

What a wallet cannot prove is recovered on a schedule instead. scripts/overlay-cron.sh runs confirmed reconciliation hourly and the confirmed backfill daily, so the node stays complete whether or not anyone opens the application.

The node also repairs its own sense of time. Records are submitted the moment they broadcast, so they enter the overlay unconfirmed, and nothing in the overlay interfaces ever tells it one was mined — resubmitting the transaction is skipped as a duplicate before anything sees it. A ten-minute sweep therefore reads each unpositioned transaction's merkle proof, checks the height against a second independent index, and hands it to the engine over the node's own /arc-ingest route. Without it a confirmed sale keeps describing itself as pending mempool state forever.

The overlay is a BRC-22/BRC-24/BRC-64 node whose topic manager and lookup resolver validate every record independently: OrdLock listing and purchase classification, owner epochs, updates, creator decisions, full history, current creative, collection-wide live proof sets, and pending-decision resolution. A CARS node runs it at https://backend.93913ed6b421f18f80e669c61239a690.projects.babbage.systems, holding the replayed confirmed namespace plus every live write since. Scheduled npm run overlay:shadow rounds compare it against the public reader and have been clean. The local LARS node under backend/ remains a disposable rebuild and development target rather than a mirror.

Opening a collection also schedules one background shadow read that projects the same collection from the derived reader and the overlay and records whether the two agree. Nothing it produces reaches the screen; it is the divergence signal for the migration rather than part of the render path.

Reads have moved. The overlay now supplies discovery and every rendered collection, and the existing reader is the fallback rather than the source. A view is always attributable to one of them, never assembled from both, and the collection view names which one it came from.

Image creatives are served from that same response rather than fetched from a content host, which makes them the only creatives in the application that are verified rather than trusted. A newly published image is displayed before its transaction confirms, proven against a live unconfirmed mint; embeds and the derived JSON reader still use content hosts, so the one-block window remains for those consumers.

Both readers make their protocol judgements with the same validators and share one display resolution, so they can disagree only about evidence. A lapsed balance, a restarted node, or a changed endpoint degrades a visitor to the existing reader rather than to an error: a funded server is an operational detail while the records are permanent.

Moving them is the current direction, and the reason is not speed: BEEF lets a client verify ownership, signatures, and history by SPV instead of trusting a service. One measured consequence already shows the gap. A newly published image ad is served by the overlay immediately, while public content hosts return 404 until the transaction confirms, and the derived reader fails the entire ad rather than part of it.

The ordered migration steps, their prerequisites, and the federation work that would remove the last discovery dependency are in OVERLAY.md.

Roadmap

  1. Complete the remaining wallet-restart, image-lifecycle, and emergency-switch beta drills, then tag the reference application.
  2. Fund a CARS project, release the prepared BRC-22/BRC-24/BRC-64 shadow node, replay the confirmed namespace into it, and keep the scheduled parity and reconciliation rounds running against both nodes while retaining fallback.
  3. Close the publish-time overlay delivery gap: a submission is queued only when a broadcast classifies as accepted, so a wallet that reports sending tells the node nothing and no queue record exists to retry. Staleness detection and the wallet repair recover it afterwards, which is why a missed purchase self-heals, but the write path should not depend on a later read noticing.
  4. Publish a read-only typed SDK/CLI or MCP interface against a stable api.adinals.com origin, then add wallet-authorized actions through an injected BRC-100 WalletInterface; never accept seeds, WIFs, or mnemonics.
  5. Evaluate inscribing a content hash with UHRP-hosted bytes for large creatives, which would cut minting cost and projection size at the price of depending on a host to retain them. This is a future record version rather than a change to version 3.
  6. Add publisher moderation, reputation, scam warnings, and clearer separation between protocol-valid, collection-approved, and publisher-featured content.
  7. Consider BRC-121 HTTP 402 payments only for hosted services such as high-availability history, proof delivery, moderation, analytics, and relays. Direct wallet actions and independent verification should remain permissionless.

See BRC100_COLLECTION_MATRIX.md for the current wallet compatibility and release gates, and OVERLAY.md for the local LARS, lifecycle-indexing, parity, and CARS deployment plan. The continuation handoff is the canonical prompt for resuming implementation without reconstructing the current state.

Public fixture policy

Only synthetic vectors or transactions already irreversibly public on mainnet may be committed. Never commit private keys, seeds, mnemonics, wallet identity keys, wallet-local action references, or a signed unbroadcast transaction package. A complete no-send Atomic BEEF can itself authorize a broadcast even when it contains no private key.

The retained production lifecycle manifest in tests/fixtures/overlay/production-lifecycle-b70c33ad.json contains public outpoints and expected derived state only. It deliberately excludes raw transactions, Atomic BEEF, and wallet-local routing data.

Two infrastructure identifiers are committed on purpose: the overlay's HTTPS endpoint in .env.production and its CARS project identifier in deployment-info.json. Neither is a credential. Browsers reveal the endpoint whenever they deliver a transaction, the overlay is a public read/submit service by design, and every administrative CARS operation is authorized by a wallet identity rather than by knowing the project's name. Generated server keys, ARC credentials, database state under local-data/, shadow reports, and every other .env file stay out of Git.

Running your own instance

This software is offered as-is under the MIT License. If you run it, the following are yours rather than the author's.

Moderation is entirely the operator's. Adinals displays user-created on-chain content, which is true of every ordinals wallet, marketplace, and explorer. Records are permanent and cannot be unpublished: creator approval gates what becomes display eligible, and nothing removes anything from the chain. An operator who opens minting or updates to third parties inherits whatever they publish, including destination URLs that may change after they were approved. Decide deliberately whether your collections use adApproval: creator, and whether their content carries links at all.

Signing keys are the operator's own. A version 3 mint is signed by the collection creator, so collections you create are signed by your key alone. The author of this software signs nothing on your behalf, and running this code does not associate your records with the author's collections or address.

No support is implied. There is no obligation to answer issues, accept contributions, maintain compatibility, or keep any interface stable between versions.

The public node is not infrastructure you can rely on. The overlay endpoint referenced in this repository is operated and funded by the author for the author's own applications. It carries no availability commitment and may change or stop without notice. An application that needs a node should run its own — the overlay is rebuildable from confirmed public transactions, which is what makes that practical.

The protocol is not the application. Version 3 records are on chain and readable by anyone. Nothing here grants exclusive use of them, and nothing prevents another reader from resolving them differently. Display rules, including which collections a given interface chooses to show, are a property of that interface rather than of the protocol.

License

The repository's software and documentation are available under the MIT License. User-published creative content remains subject to its own rights and responsibilities.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages