@xanaverse/contracts
v1.4.0
Published
BSV smart contracts for trustless social media - covenant-enforced posts (lockable v2), upvotes, replies, user identity marketplace, and time-locked payments
Downloads
333
Maintainers
Readme
@xanaverse/contracts
BSV smart contracts for trustless social media. Covenant-enforced posts, upvotes, replies, user identity, and time-locked value, where payment and ownership rules are validated by the Bitcoin network, not platforms.
Install
npm install @xanaverse/contractsPeer dependency: Requires scrypt-ts ^1.4.0
Quick Start
import {
PostAnchor,
PostAnchorArtifact,
parsePostAnchor,
PROTOCOL_ID
} from '@xanaverse/contracts'
// Load artifact before using contract
PostAnchor.loadArtifact(PostAnchorArtifact)
// Parse a post from on-chain data
const post = parsePostAnchor(lockingScriptHex)
console.log(post.content, post.creatorIdentityKey)Source Code
This package includes the raw sCrypt smart contract source files in src/contracts/. You can:
- Audit the covenant logic that enforces all payment and identity rules
- Verify artifacts by recompiling with
npm run compile - Study production covenant patterns (two-tx pattern, commitment privacy, SMT, CLTV timelocks)
Exports
Contract Classes
| Contract | Purpose |
|----------|---------|
| PostAnchor | Posts with content hash verification and tags (v1, current default) |
| PostAnchorV0 | Legacy post format (frozen, parsing/reclaim compatibility only) |
| PostAnchorV2 | Lockable posts: CLTV-gated reclaim, committed sats, content modes |
| UpvoteProof | Upvote proofs with covenant-enforced creator payments |
| ReplyProof | Reply proofs with covenant-enforced parent payments |
| DownvoteProof | Downvote proofs (creator still receives payment) |
| UserRegistry | SMT-based user number registration (singleton) |
| UserProof | Tradeable user number ownership NFT with built-in marketplace |
| TimeLockedGift | One-shot time-locked payment covenant (network-enforced unlock date) |
Artifacts
Compiled Bitcoin Script for each contract. Load before instantiating:
import {
PostAnchorArtifact,
PostAnchorV0Artifact,
PostAnchorV2Artifact,
UpvoteProofArtifact,
ReplyProofArtifact,
DownvoteProofArtifact,
UserRegistryArtifact,
UserProofArtifact,
TimeLockedGiftArtifact
} from '@xanaverse/contracts'
PostAnchor.loadArtifact(PostAnchorArtifact)
PostAnchorV2.loadArtifact(PostAnchorV2Artifact)
UpvoteProof.loadArtifact(UpvoteProofArtifact)
UserRegistry.loadArtifact(UserRegistryArtifact)
UserProof.loadArtifact(UserProofArtifact)
TimeLockedGift.loadArtifact(TimeLockedGiftArtifact)Protocol Constants
import {
PROTOCOL_ID,
PROTOCOL_KEY_ID,
PROTOCOL_COUNTERPARTY,
PROTOCOL_BASKETS,
PROTOCOL_SHARED_CONFIG
} from '@xanaverse/contracts'
// BRC-42 key derivation
PROTOCOL_ID // [0, 'xanaverse']
PROTOCOL_KEY_ID // '1'
PROTOCOL_COUNTERPARTY // 'self'
// UTXO basket names (BRC-100)
PROTOCOL_BASKETS.posts // 'xanaverse-posts'
PROTOCOL_BASKETS.upvotes // 'xanaverse-upvotes'
PROTOCOL_BASKETS.replies // 'xanaverse-replies'
PROTOCOL_BASKETS.downvotes // 'xanaverse-downvotes'Post Versions
| Version | Class | Status | Notes |
|---------|-------|--------|-------|
| v0 | PostAnchorV0 | Frozen | Legacy posts; parsing and reclaim only |
| v1 | PostAnchor | Current | Adds tags and version props |
| v2 | PostAnchorV2 | Live | Adds lockUntil, contentMode, committedSats |
Parsing Posts (version-aware)
parsePostAnchor detects the version from the script itself (newest first, keyed
on the contract's recovered version prop) and returns a normalized shape:
import {
parsePostAnchor,
isPostAnchor,
getPostAnchorInstance
} from '@xanaverse/contracts'
import type { ParsedPostAnchor } from '@xanaverse/contracts'
if (isPostAnchor(lockingScript)) {
const post: ParsedPostAnchor = parsePostAnchor(lockingScript)
console.log(post.version) // 0 | 1 | 2
console.log(post.content) // Post text
console.log(post.creatorProtocolPubKey) // BRC-42 derived key
console.log(post.creatorIdentityKey) // Root identity
console.log(post.contentHash) // SHA-256 hash
console.log(post.createdAt) // Timestamp
console.log(post.parentHash) // Parent post (for threads)
// v1+ only
console.log(post.tags) // Comma-separated tags
// v2 only (undefined for v0/v1)
console.log(post.lockUntil) // Unix-time CLTV lock; 0n = unlocked
console.log(post.contentMode) // 1n full | 2n hash-only | 3n hash+data
}
// Get raw contract instance (for advanced operations like reclaim).
// v2 posts MUST reclaim via the v2 instance: its reclaim() carries the
// CLTV gate and pays out committedSats.
const { version, contract } = getPostAnchorInstance(lockingScript)Resolving Engagement Proof Instances
Version-aware instance resolution for upvotes, downvotes, and replies (used to dispatch reclaims). All engagement protocols are currently v1:
import {
getUpvoteProofInstance,
getDownvoteProofInstance,
getReplyProofInstance
} from '@xanaverse/contracts'
const { version, contract } = getUpvoteProofInstance(lockingScript)
await contract.methods.reclaim(...)Contract Properties
PostAnchor (v1)
interface PostAnchorState {
creatorProtocolPubKey: PubKey // BRC-42 derived identity
creatorIdentityKey: ByteString // Root identity (display name)
content: ByteString // Post text (max 280 chars)
contentHash: ByteString // SHA-256 of content
nonce: ByteString // Privacy nonce
createdAt: bigint // Unix timestamp
parentHash: ByteString // Parent post hash (threads)
tags: ByteString // Comma-separated tags
targetDifficulty: bigint // PoW target (immutable)
version: bigint // Contract version
}PostAnchorV2 (lockable posts + content modes)
Everything in v1, plus three stateful props that the covenant binds forward unchanged from deploy:
interface PostAnchorV2Extras {
lockUntil: bigint // Unix-time lock; 0n = unlocked
contentMode: bigint // 1n full | 2n hash-only | 3n hash+data
committedSats: bigint // Sats frozen in the anchor until lockUntil
}
// Content mode constants
PostAnchorV2.MODE_FULL // 1n - body on-chain at publish (v1 behaviour)
PostAnchorV2.MODE_HASH_ONLY // 2n - only contentHash on-chain; body off-chain
PostAnchorV2.MODE_HASH_DATA // 3n - contentHash at publish, body via addContent (3rd tx)Methods:
publish(sig, creatorIdentityKey, content, tags, contentHash, createdAt, parentHash)reveals the post and rebuilds the anchor atcommittedSats. The post is visible immediately; only the sats are frozen.addContent(sig, content)(mode 3 only) reveals the body in a third transaction and provessha256(content) == contentHashon-chain.reclaim(sigCreator)is CLTV-gated: the covenant assertsthis.timeLock(lockUntil), so the spending transaction must carrynLockTime >= lockUntilwith a non-final input sequence, and the network refuses to confirm it early. PayscommittedSatsback to the creator.
How the timelock works on BSV: OP_CHECKLOCKTIMEVERIFY is a NOP on BSV
post-Genesis (Feb 2020). What Genesis kept is consensus enforcement of
transaction finality: a tx with a future nLockTime and a non-final input
sequence will not be mined until median-time-past passes the lock. The
covenant's timeLock() assertion reads the spending tx's own
nLockTime/nSequence from the sighash preimage and forces the spender to
build a tx the network refuses to confirm early.
TimeLockedGift
A standalone, one-shot time-locked payment covenant. Pay someone such that
they fully own the claiming key, but the network itself refuses to let them
spend the coins until lockUntil (same timeLock() mechanism as
PostAnchorV2 above).
interface TimeLockedGiftState {
recipient: PubKey // 33-byte compressed pubkey; their key authorizes the claim
lockUntil: bigint // Unix timestamp (>= 500000000) before which coins can't move
amount: bigint // Exact sats paid to the recipient on unlock
}Method: unlock(sig) asserts the timelock has elapsed, checks the
recipient's signature, and pins the output to P2PKH(recipient, amount). The
spending tx-builder must set nLockTime >= lockUntil and a non-final input
sequence (e.g. 0xfffffffe).
import { TimeLockedGift, TimeLockedGiftArtifact } from '@xanaverse/contracts'
TimeLockedGift.loadArtifact(TimeLockedGiftArtifact)
const gift = new TimeLockedGift(
PubKey(recipientPubKeyHex),
BigInt(unlockUnixTime),
BigInt(satoshis)
)UpvoteProof
interface UpvoteProofState {
upvoterProtocolPubKey: PubKey // Who upvoted (BRC-42)
upvoterIdentityKey: ByteString // Upvoter display name
contentHash: ByteString // Post being upvoted
creatorProtocolPubKey: PubKey // Post creator (payment recipient)
creatorIdentityKey: ByteString // Creator display name
paymentAmount: bigint // Sats paid to creator (min 100)
nonce: ByteString // Privacy nonce
}ReplyProof
interface ReplyProofState {
replierPubKey: PubKey // Who replied (BRC-42)
replier: ByteString // Replier display name
replyContent: ByteString // Reply text
replyContentHash: ByteString // SHA-256 of reply
parentContentHash: ByteString // Parent being replied to
parentProtocolPubKey: PubKey // Parent creator (payment recipient)
parentIdentityKey: ByteString // Parent creator display name
paymentAmount: bigint // Sats paid to parent (min 50)
createdAt: bigint // Unix timestamp
nonce: ByteString // Privacy nonce
}UserRegistry (Singleton)
interface UserRegistryState {
nextUserId: bigint // Next sequential ID to assign
registeredKeysRoot: ByteString // SMT root of registered identities
treasuryAddress: PubKeyHash // Registration fee recipient
platformAddress: PubKeyHash // Marketplace fee recipient
}
// Constants
UserRegistry.REGISTRATION_FEE = 1000n // satoshis
UserRegistry.SMT_DEPTH = 256 // full sha256 keyspaceUserProof (Tradeable NFT)
interface UserProofState {
// Immutable (provenance)
userId: bigint // Assigned user number (never changes)
originalRegistrant: PubKey // First registrant
// Mutable (ownership)
ownerPubKey: PubKey // Current owner's protocol key
ownerIdentityKey: PubKey // Current owner's identity key
isForSale: boolean // Listing status
salePrice: bigint // Price in satoshis
// Platform
platformAddress: PubKeyHash // Marketplace fee recipient
}
// Constants
UserProof.PLATFORM_FEE_BPS = 100n // 1% marketplace feeMarketplace methods (all covenant-enforced, owner signature first):
list(sig, price)- list the user number for saleupdatePrice(sig, newPrice)- change the asking pricedelist(sig)- remove the listingbuy(sig, newOwner, newOwnerIdentityKey)- purchase; covenant validates the seller payout, tx builder adds the 1% platform fee outputtransfer(sig, newOwner, newOwnerIdentityKey)- direct transfer, no paymentreclaim(sig)- burn the UTXO
Usage Examples
Parse Contract from Transaction Output
import { PostAnchor, PostAnchorArtifact } from '@xanaverse/contracts'
// Load artifact once at startup
PostAnchor.loadArtifact(PostAnchorArtifact)
// Parse from locking script
const post = PostAnchor.fromLockingScript(lockingScriptHex)
console.log(post.content.toString())
console.log(post.creatorProtocolPubKey.toString())Derive Earnings from Proof UTXOs
import { UpvoteProof, ReplyProof } from '@xanaverse/contracts'
// Parse actual payment amounts (don't estimate!)
let totalEarnings = 0n
for (const utxo of upvoteUTXOs) {
const proof = UpvoteProof.fromLockingScript(utxo.script)
totalEarnings += proof.paymentAmount // Could be 100, 150, 200+ sats
}
for (const utxo of replyUTXOs) {
const proof = ReplyProof.fromLockingScript(utxo.script)
totalEarnings += proof.paymentAmount // Could be 50, 75, 100+ sats
}Use Protocol Constants for Wallet Integration
import { PROTOCOL_SHARED_CONFIG } from '@xanaverse/contracts'
// Get XanaVerse protocol key (BRC-42)
const { publicKey } = await wallet.getPublicKey({
protocolID: PROTOCOL_SHARED_CONFIG.protocolID,
keyID: PROTOCOL_SHARED_CONFIG.keyID,
counterparty: PROTOCOL_SHARED_CONFIG.counterparty
})Parse User Registry State
import { UserRegistry, UserRegistryArtifact } from '@xanaverse/contracts'
UserRegistry.loadArtifact(UserRegistryArtifact)
const registry = UserRegistry.fromLockingScript(lockingScriptHex)
console.log(`Users registered: ${registry.nextUserId - 1n}`)
console.log(`SMT root: ${registry.registeredKeysRoot}`)Parse User Proof (NFT)
import { UserProof, UserProofArtifact } from '@xanaverse/contracts'
UserProof.loadArtifact(UserProofArtifact)
const proof = UserProof.fromLockingScript(lockingScriptHex)
console.log(`User #${proof.userId}`)
console.log(`Owner: ${proof.ownerIdentityKey}`)
console.log(`For sale: ${proof.isForSale} @ ${proof.salePrice} sats`)
console.log(`Original registrant: ${proof.originalRegistrant}`)TypeScript Types
import type { ParsedPostAnchor } from '@xanaverse/contracts'
// Full type definitions included for all exportsArchitecture
These contracts implement the XanaVerse trustless social media protocol:
Content Layer:
- Posts create immutable content anchors with cryptographic authorship
- v2 posts can additionally freeze sats in the anchor until a network-enforced unlock time, and choose how much content lives on-chain (full / hash-only / hash+deferred-reveal)
Engagement Layer:
- Upvotes create proof UTXOs with covenant-enforced creator payments
- Downvotes create proof UTXOs (creator still receives payment)
- Replies create proof UTXOs with covenant-enforced parent payments
Identity Layer:
- UserRegistry singleton manages sequential user number assignment with SMT duplicate prevention
- UserProofs are tradeable NFTs representing user number ownership, with a built-in covenant marketplace (list / buy / transfer)
Value Layer:
- TimeLockedGift pays sats that the recipient owns but cannot move until a network-enforced unlock time
Trust Model:
- Counts are derived by querying proof UTXOs (indexers can't fake them)
- All payment rules are enforced by Bitcoin Script at the network level
- User identities are cryptographically provable via Sparse Merkle Tree
- Timelocks are enforced by transaction finality consensus, not application code
Related
- sCrypt Documentation - Smart contract framework
License
MIT
