# peck-social-token — DRAFT

**Status:** Draft. Substantial design ahead of any implementation. Companion spec to `peck-social-v1.md`, not a replacement.
**Author:** Thomas (kryp2) and contributors.
**Started:** 2026-05-05.
**License:** MIT.

## 0. Why this document exists alongside peck-social/v1

`peck-social-v1.md` specifies how Bitcoin Schema (B + MAP + AIP, all in OP_RETURN) is used in a BRC-100-native way. It is the **bridge profile** — it lets every legacy Bitcom parser continue to read peck.to-family content while modern indexers can verify signatures and resolve identity properly.

`peck-social-token` (this document) specifies a **fully sovereign, fully on-chain alternative model** where each social entity is a stateful UTXO whose locking script encodes both its data and the rules for its mutation. The model leverages BSV's restored Satoshi-era opcodes, the OP_PUSH_TX trick for stateful contracts, the [Rúnar](https://github.com/icellan/runar) multi-language compiler, and the 1Sat-ordinals output convention.

The two specs are designed to coexist. v1 covers high-volume, lightweight, attribution-only operations (likes, follows, tags, channel messages). This token spec covers ownership-bearing, mutable-state, or value-carrying entities (profiles, identity claims, posts-as-assets, BRC-52 certificates, cross-walking claims). Implementations choose per entity which model fits, guided by the hybrid policy in §5.

Most importantly: this document records a **design destination**, not an immediate implementation roadmap. v1 is the bridge that ships now. This is the bigger architectural commitment that depends on Rúnar maturity, indexer UTXO-set tooling, and wallet-side stateful-contract spend support landing across the BRC-100 ecosystem.

---

## 1. Layer stack

```
+--------------------------------------------------+
|  peck-social-token (this document)               |  entity types, contract templates,
|                                                  |  hybrid token/OP_RETURN policy
+--------------------------------------------------+
|  Rúnar contract framework                        |  multi-language → identical Bitcoin Script;
|                                                  |  StatefulSmartContract for OP_PUSH_TX
+--------------------------------------------------+
|  1Sat-ordinals output convention                 |  data in P2PKH-spendable locking script;
|                                                  |  every entity = a 1-sat transferable token
+--------------------------------------------------+
|  Restored Satoshi-era opcodes                    |  OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_2MUL,
|                                                  |  OP_2DIV, OP_LSHIFTNUM, OP_RSHIFTNUM, etc.
+--------------------------------------------------+
|  OP_PUSH_TX trick                                |  scripts inspect their own transaction;
|                                                  |  enables cryptographically-enforced rules
+--------------------------------------------------+
|  BRC-100 wallets (BRC-43 protocol IDs, BRC-52    |  identity, signing, certificates,
|  certs, BRC-104 content refs, BRC-77 messages)   |  off-chain content addressing
+--------------------------------------------------+
|  BSV chain (TXs, UTXOs, OP_RETURN scaffolding)   |
+--------------------------------------------------+
```

The `peck-social-v1` profile sits at the same conceptual position as this document but consumes only OP_RETURN (the bottom rail), not the stateful-contract layers above. Both can coexist on the same chain and same indexer.

---

## 2. Contract framework — Rúnar over sCrypt

**Recommendation:** Use [Rúnar](https://github.com/icellan/runar) as the primary contract compiler for this profile.

**Why over sCrypt:**

- **Multi-language compilation.** peck.to's stack spans TypeScript (frontend), Go (peck-indexer-go), Python (peck-web), and increasingly Rust for tooling. Rúnar's seven reference compilers (TS, Go, Rust, Java, Ruby, Python, Zig) produce byte-identical Bitcoin Script. Each layer of the stack writes contract verifications in its native language; sCrypt forces a single TS-DSL pipeline regardless of where the verification is needed.
- **Auditability via nanopass.** Six composable, independently-auditable compilation passes. For contracts that hold value (BRC-52 certs, profile state, identity claims), this matters more than for one-off OP_RETURN dataposts.
- **Native language semantics, no decorators.** Code reads as code in the chosen language. Easier for new contributors than learning sCrypt's `@method`/`@prop` decorator system.
- **Cross-compiler parity testing.** Reference suites verify all seven compilers produce identical output, which closes a class of "the framework changed underneath us" risks.

**Honest cons:**

- Younger than sCrypt; smaller production track record.
- icellan as primary maintainer (though the multi-language-cross-compiler design somewhat mitigates bus-factor since each compiler is independently buildable).
- Tooling ecosystem (debuggers, contract explorers, verifier services) is more sCrypt-aware today.

For peck-social-token, Rúnar is the right choice on engineering merits. We document fallback to sCrypt as an option for any entity type where Rúnar tooling is insufficient at the time of implementation.

---

## 3. Restored opcodes — what we actually use them for

BSV restored Satoshi-era opcodes that BTC removed in 2010-2011. The relevant ones for this profile, with concrete uses:

| Opcode | Use in this profile |
|---|---|
| `OP_SUBSTR` | Direct byte-slicing of OP_PUSH_TX preimage. Replaces 6-10 OP_SPLIT-stack manipulations with one opcode. Used in every stateful contract that reads "next output's lock-script" or "input #N outpoint". |
| `OP_LEFT` / `OP_RIGHT` | Take the first/last N bytes of a stack item. Used to extract fixed-size fields (txid prefix, amount low-bytes) from serialized TX. |
| `OP_2MUL` / `OP_2DIV` | Multiply/divide-by-2. Used for compact bit-packed state encoding (e.g., several flags in a single byte slot). |
| `OP_LSHIFTNUM` / `OP_RSHIFTNUM` | Variable-amount bit shifts. Same as above but with runtime shift counts. |
| `OP_CAT` / `OP_SPLIT` | Already widely used; concatenate / split bytes. Foundation of OP_PUSH_TX preimage construction. |

The combined effect: stateful contract scripts that would have been 3-5KB without these (using only the Bitcoin-2009 opcode set) come down to 500B-1.5KB. Per-update fee scales linearly with script bytes, so per-update fee drops 3-5×.

This brings token-state per-update cost into a range where it is competitive with OP_RETURN posts even before considering batching or hybrid optimizations.

---

## 4. The 1Sat-ordinals output convention

Every entity in this profile is a UTXO whose output structure follows the 1Sat-ordinals pattern:

```
Output value: 1 satoshi (dust minimum)
Locking script:
    <state-data-pushes interleaved with OP_DROPs>
    OP_DUP OP_HASH160 <owner-pubkey-hash> OP_EQUALVERIFY OP_CHECKSIG
                                      (or for stateful contracts:)
    <state-data-pushes interleaved with OP_DROPs>
    <stateful-contract-template-with-OP_PUSH_TX-rules>
```

The OP_DROP-pattern places state on the stack at script-execution time and immediately drops it, so the spend of the UTXO succeeds based only on the trailing P2PKH (or stateful contract) check. Data lives "above" the spend logic, not in an unspendable OP_RETURN burn.

**Why 1Sat specifically:**

- 1 satoshi per UTXO is the dust minimum on BSV — minimum economic cost for the output itself. (Compare with OP_RETURN: 0 sat, but the output is permanently unspendable; the satoshi is "burned" indirectly via fees on the transaction itself.)
- The 1-sat convention is widely understood by existing 1Sat-ordinals indexers and wallets, so peck-social-token entities can ride existing infrastructure rather than reinventing tracking.
- The output is **transferable** — entities can change ownership via standard P2PKH spends if their rules allow.

---

## 5. Hybrid policy — token-state vs. OP_RETURN per entity

Not every entity benefits from token-state. The friction (TX construction complexity, UTXO tracking, concurrency contention) outweighs the benefits for high-volume, attribution-only actions. This profile codifies which entities go which way:

| Entity | Model | Rationale |
|---|---|---|
| `Profile` | Token-state | One UTXO per user that gets re-spent on every update. Ownership matters; cryptographic enforcement of "only owner can update" matters. |
| `Post` | Token-state | Posts can be transferred (sold, gifted), can be burned (deleted) cryptographically. |
| `Reply` | Token-state | Same reasoning as Post; reply is just a Post with `parent_ref`. |
| `IdentityClaim` (cross-walking) | Token-state | Binding between two pubkeys; revocation requires both signatures. State-machine semantics critical. |
| `BRC52Cert` | Token-state | Certificate with revocation logic; issuer rules cryptographically enforced. |
| `Message` (DM) | Token-state | Encrypted; ownership of decryption-token tracked. Optional — could be OP_RETURN with BRC-78 envelope if simpler suffices. |
| `Like` | OP_RETURN | High volume (10-100× post volume). Token-ownership semantics give little value. v1-style attribution is enough. |
| `Unlike` | OP_RETURN | Counterpart to Like; same reasoning. |
| `Follow` / `Unfollow` | OP_RETURN | High volume; relational not asset-like. |
| `Tag` (retroactive tagging) | OP_RETURN | Append-only metadata. |
| `Channel message` (group chat) | OP_RETURN | High volume, ephemeral context. |

This split is a starting recommendation, not a contract. Apps may move entities between modes if they have reason to, but they SHOULD document and motivate the choice.

---

## 6. Entity contract templates (sketches)

These are sketches in pseudo-Rúnar (close to TypeScript). Full implementations are deferred to the framework-specific repos when they are written.

### 6.1 Profile

```typescript
@stateful
class Profile extends StatefulSmartContract {
    // Immutable after first mint
    readonly identityKey: PublicKey

    // Mutable state
    public displayName: string  // max 64 utf8 bytes
    public bio: string          // max 280 utf8 bytes, '' if absent
    public avatarRef: BRC104Ref | null
    public certRefs: TokenRef[] // pointers to BRC-52 cert tokens
    public version: bigint      // monotonically incrementing

    @public
    update(
        newDisplayName: string,
        newBio: string,
        newAvatarRef: BRC104Ref | null,
        newCertRefs: TokenRef[],
        ownerSig: Signature,
    ) {
        // Rule 1: identityKey is immutable (enforced by template)
        // (the template generates a new locking script that hard-codes
        // the same identityKey; OP_PUSH_TX checks next output uses it)

        // Rule 2: monotonic version
        assert(this.nextVersion() == this.version + 1n)

        // Rule 3: only owner can update
        assert(this.checkSig(ownerSig, this.identityKey))

        // Rule 4: avatarRef hash must bind (if present)
        if (newAvatarRef != null) {
            assert(newAvatarRef.hash == sha256(newAvatarRef.resolveBytes()))
        }

        // Rule 5: bounds
        assert(newDisplayName.length <= 64)
        assert(newBio.length <= 280)
    }

    @public
    burn(ownerSig: Signature) {
        // Profile can be revoked entirely; rare but supported
        assert(this.checkSig(ownerSig, this.identityKey))
        assert(this.noContinuation())
    }
}
```

### 6.2 Post

```typescript
@stateful
class Post extends StatefulSmartContract {
    readonly author: PublicKey
    readonly content: ByteString | BRC104Ref  // inline or reference (mutually exclusive)
    readonly parentRef: TokenRef | null         // for replies
    readonly timestamp: bigint                  // author-claimed
    readonly tags: string[]                     // immutable

    @public
    transfer(newOwner: PublicKey, currentOwnerSig: Signature) {
        // Posts are transferable; new locking script binds to newOwner's P2PKH.
        // Author field stays the same forever (immutable).
        assert(this.checkSig(currentOwnerSig, this.currentOwner()))
    }

    @public
    burn(authorSig: Signature) {
        // Posts can only be burned by the original author.
        // Burning produces no continuation output — UTXO disappears.
        assert(this.checkSig(authorSig, this.author))
        assert(this.noContinuation())
    }
}
```

### 6.3 IdentityClaim (cross-walking)

```typescript
@stateful
class IdentityClaim extends StatefulSmartContract {
    readonly fromKey: PublicKey
    readonly toKey: PublicKey
    readonly direction: 'forward' | 'backward'  // K1→K2 or K2→K1

    @public
    revoke(fromSig: Signature, toSig: Signature) {
        // Revocation requires BOTH keys to sign. This is critical:
        // a malicious K2 cannot unilaterally claim K1's history was
        // theirs and then disavow once they've benefited. Both halves
        // of the binding are needed for either to break it.
        assert(this.checkSig(fromSig, this.fromKey))
        assert(this.checkSig(toSig, this.toKey))
        assert(this.noContinuation())
    }
}
```

### 6.4 BRC52Cert

```typescript
@stateful
class BRC52Cert extends StatefulSmartContract {
    readonly issuer: PublicKey
    readonly subject: PublicKey
    readonly type: string                        // e.g., 'peck.to/identity/v1'
    readonly handleHash: ByteString              // hash(handle + per-user-salt), per spec §5.3
    readonly fields: Map<string, string>         // attested attributes
    readonly issuedAt: bigint
    readonly expiresAt: bigint | null            // null = no expiry

    @public
    revoke(issuerSig: Signature) {
        // Only the issuer can revoke a cert they issued.
        assert(this.checkSig(issuerSig, this.issuer))
        assert(this.noContinuation())
    }

    @public
    transferToNewSubjectKey(
        oldSubjectSig: Signature,
        newSubjectSig: Signature,
        issuerSig: Signature,
    ) {
        // For "I rotated my key but kept the same identity" — three-way handshake.
        // Old subject signs (acknowledging rotation), new subject signs (claiming new key),
        // issuer signs (re-attesting the binding).
        assert(this.checkSig(oldSubjectSig, this.subject))
        // ... new subject and issuer checks
    }
}
```

---

## 7. OP_RETURN entities — alignment with peck-social/v1

Entities that use OP_RETURN in this profile (Like, Unlike, Follow, Unfollow, Tag, Channel-message) follow the **peck-social/v1** spec exactly. Same canonical AIP signing, same MAP-key namespace, same URN dispatch table.

This means a single peck-social client implementation can produce both:
- Token-state outputs (for entities listed in §5 as token-state)
- v1-conformant OP_RETURN outputs (for entities listed as OP_RETURN)

— in the same transaction if needed. Indexers parse both side-by-side.

---

## 8. Content addressing — BRC-104 still applies

Content > 1KB (especially binary) MUST use BRC-104 references regardless of which model the surrounding entity uses. The hash field in the BRC-104 reference is then hash-locked into the entity's state (whether token-state or OP_RETURN), and contract rules can validate hash-content binding via OP_PUSH_TX where applicable.

The privacy + regulatory posture (peck-social/v1 §4.4) carries forward unchanged: personal data lives at BRC-104 hosts, only hashes on chain, GDPR-compliance-by-design.

---

## 9. Identity — token-native binding

The cert layer (§6.4) makes BRC-52 certificates first-class on-chain entities. A profile-token (§6.1) holds an array of `certRefs` pointing to active cert-tokens. A consumer rendering the profile can:

1. Walk to each cert-token UTXO.
2. Verify it is unspent (i.e., not revoked).
3. Verify the issuer's signature in the cert-token state.
4. Decide based on local trust whether to surface the cert's attested fields.

When a cert is revoked, its UTXO is spent (no continuation). Indexers MUST treat spent cert-tokens as revoked and stop displaying their attested fields. This is faster + more reliable than any off-chain revocation list.

Cross-walking (§6.3) produces a directed graph of identity-claim-tokens, all of which are unspent UTXOs while the binding is live. Walking the graph from any pubkey gives the full set of bindings the user has published. Revocation requires bilateral signature so neither party can unilaterally rewrite history.

---

## 10. Indexer expectations

A peck-social-token-aware indexer must, in addition to peck-social/v1 capabilities:

1. **Track UTXO-set per entity type.** For each token entity (Profile, Post, Reply, IdentityClaim, BRC52Cert, Message), maintain "currently unspent" view keyed by some canonical lookup (e.g., for Profile: identityKey → latest UTXO outpoint).
2. **Parse state out of locking scripts.** Each entity-type has a known locking-script template; indexer extracts state values from the data-pushes preceding the contract template.
3. **Detect state transitions.** On parsing a TX that spends an entity-token-UTXO, the indexer matches old vs. new (or detects burn = no continuation) and logs the transition.
4. **Combine with OP_RETURN parsing.** A single TX may have both token-state outputs and OP_RETURN outputs; both are parsed independently.
5. **Verify contract validity at parse time** (optional but recommended). Re-execute the script in a sandbox to ensure rules were satisfied. BSV nodes do this anyway for consensus, so this is a paranoia check.

Existing 1Sat-ordinals indexers already do (1) and (2); peck-social-token-aware indexers extend with (3) and the entity-specific templates.

---

## 11. Migration from peck-social/v1

Implementations adopting this profile do not need to migrate existing OP_RETURN content. The v1 profile remains canonical for any pre-token-spec data. Token-state entities are produced going forward; legacy entities continue to be parsed under v1 rules.

For users with existing v1 profiles who want token-state profiles: a one-time mint TX produces a Profile-token UTXO with state copied from the latest v1 profile-tx. The v1 profile-tx remains on chain (immutable) but rendering picks up the token-state output as authoritative thereafter.

For platforms that want to fully migrate: scan v1 history for each entity type, mint corresponding token-state UTXOs in batches. Cost is non-trivial (1.5M+ existing entities × 1 sat + fee per entity ≈ a non-trivial sum) but bounded.

---

## 12. Open questions

- **Wallet-toolbox integration for spending stateful contracts.** BRC-100 wallets currently have no standard "spend a stateful contract"-API beyond ad-hoc applications. peck-social-token implementations need a path through this — either Rúnar/sCrypt providing wallet-integration libraries, or an extension to BRC-100 itself.
- **UTXO contention under concurrent updates.** What does the UX look like when two clients try to update the same Profile-token simultaneously? Suggested: optimistic submit + retry-with-rebase; needs spec'ing.
- **Aggregator contracts for high-volume operations.** For Likes (which we put in OP_RETURN), an aggregator contract that batches many likes into a single update could reduce TX volume. Worth exploring as an optional optimization, not a default.
- **Indexer-state-snapshot format.** When token-state is the canonical truth, indexers need a way to publish state snapshots that other indexers can verify against without re-walking the entire UTXO history. Some standard like "Merkle-tree of entity-UTXO-roots at block N" worth considering.
- **Cross-app composability.** Two apps using peck-social-token could share entity-types, but their hybrid policies might disagree (one calls Like a token, the other calls it OP_RETURN). How do they interoperate? Suggested: token-form-of-Like is a strict superset of OP_RETURN-form-of-Like, so an app that doesn't recognize the token form simply skips it.
- **OP_RETURN coexistence indexer policy.** When an entity exists in both v1 OP_RETURN form (legacy) and token-state form (post-migration), which is authoritative? Recommended: token-state wins where present, but the v1 record persists for audit.

---

## 13. Acknowledgments

This profile builds on:

- **Bitcoin Schema** (rohenaz / b-open-io) — the foundational B/MAP/AIP stack and the schema.org-rooted philosophy of structured-data-on-chain.
- **1Sat-ordinals** convention — the data-in-spendable-output pattern that makes token-state economic.
- **OP_PUSH_TX** technique — the script-introspection trick that makes stateful Bitcoin contracts tractable (community work; sCrypt and Rúnar both formalize use of this).
- **Rúnar** (icellan) — the multi-language compiler that lets peck.to's heterogeneous stack write contracts natively.
- **sCrypt** team (BSV Association) — for years of refining the Bitcoin Script smart-contract programming model.
- **BRC-100 ecosystem** — wallets, BRC-43 protocol IDs, BRC-52 certificates, BRC-77 signed messages, BRC-104 content addressing, all of which compose into the wallet-and-content layer this profile sits on.
- **Craig Wright's "use the language as designed" critique of OP_RETURN** — uncomfortable to credit but the correct technical observation. Token-state is what Bitcoin's script language was always built to enable; OP_RETURN was a 2014 compromise.
- **The schema.org and HTML+microdata communities** — the underlying philosophy that data-types should be open, parsable, and composable across implementations.

The errors in this document are ours; the foundations are theirs.
