deviant/contracts/CoinMBA.cash
Local Dev 9a24a19c94 feat(mba): rename covenants to CoinMBA / ShareMBA / OracleMBA + reserve DynamicMBA
Clean names that match the Mathematically Backed Asset framing (which came
first; the covenant source files were a later exploratory implementation
whose historical names outlived their usefulness). Rename is bytecode-
preserving — the chipnet-validated 52,998-swap proof carries over unchanged
because cashscript's `contract Name { }` is a source-level identifier that
doesn't enter the compiled script.

Source renames (git mv):
  contracts/DeviantOpenMintFT.cash  → contracts/CoinMBA.cash    (templateId 0x00)
  contracts/DeviantShareMBA.cash    → contracts/ShareMBA.cash   (templateId 0x01)
  contracts/DeviantOracleMBA.cash   → contracts/OracleMBA.cash  (templateId 0x02)

Deleted:
  contracts/ArtemisionMintFT.cash   (byte-identical duplicate of DeviantOpenMintFT;
                                     "Artemision" lives on only as the wizard UI
                                     concept, not a distinct template)

New:
  contracts/DynamicMBA.cash         (templateId 0x03 — reserved for the Dynamic
                                     Rate Asset covenant; scaffold only, rejects
                                     every call. DRA is distinct from FIATμ:
                                     FIATμ = dynamic-rate + oracle, DRA = pure
                                     state-driven, no oracle, reflective
                                     against a shared pool)

Compiled artifacts rebuilt via `cashc` and shipped to:
  - design/potidaea/src/artifacts/   (live client consumption)
  - site/deviant/docs/mba/artifacts/ (public download URLs for third-party
                                     wallet integration; also deployed to
                                     silentmode primary + Sia fallback)

Bytecode verification (sha256-of-bytecode):
  CoinMBA     == DeviantOpenMintFT  ✓ (6a9ae89b...)
  ShareMBA    == DeviantShareMBA    ✓
  OracleMBA   == DeviantOracleMBA   ✓

Client code updates (live surface only — historical scripts, snapshots,
knowledgebase etc. keep their original names since they reference specific
chipnet tx hashes that happened under those names):
  - design/potidaea/src/orbital.ts      — artifact imports
  - design/potidaea/src/genesis.ts      — artifact imports + Artemision deprecation note
  - design/potidaea/src/registry.ts     — artifact imports + dispatch doc + 0x03 note
  - design/potidaea/src/publish.ts      — TEMPLATE_* constants renamed + 0x03 added
  - design/potidaea/src/main.ts         — TEMPLATE_* usages renamed

Docs updates:
  - site/deviant/docs/mba/index.html         — templates table expanded to 4 rows,
                                              DRA vs FIATμ note added, dev-section
                                              code-reference lines updated
  - site/deviant/docs/mba/artifacts/index.html — one card per new artifact, DRA
                                                scaffold flagged "not deployable"

Follow-up (not in this commit): the chipnet reseed. Current ASM/ASM2
instances were minted under the DeviantOpenMintFT covenant address and
remain redeemable (bytecode is identical, addresses derive to the same
value). A fresh-start deploy that mints new CoinMBA instances under the
new identity is a separate step requiring real chipnet BCH funding +
Katalogos publish. ASM/ASM2 seed entries stay in orbital.ts SEED_MBAS
until that follow-up ships.

Also deferred: the ~15 experimental MBA-adjacent covenants
(DeviantOpenMintFT_Backdoor, _Escalating, _NoPeg, _Whitelist,
DeviantOracleMBAFloor, _DraFee, DeviantManagedMintFT,
DeviantTieredMintOracleFT, OraclePricedVault, ExpiringMBA,
DeviantPlatformMBA, etc.) stay in contracts/ at their current names —
none are user-facing templates, and their fate (archive, promote,
retire) deserves its own taxonomy session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:00:15 +02:00

119 lines
6.1 KiB
Text

pragma cashscript ^0.12.0;
// DeviantOpenMintFT — open-mint covenant for a single CashTokens FT category.
// Per ADR-0006 (native CashTokens + CashScript composite), corrected by
// the chipnet finding in snapshot 0008 (FT supply is fixed at genesis).
//
// Architecture: RESERVOIR MODEL.
// CashTokens consensus (CHIP-2022-02-CashTokens) fixes FT supply at
// genesis. A minting NFT lets you create additional NFTs of the
// category, but NOT additional FTs. So "open mint" here means a
// permissionless transfer-from-reservoir, not on-demand FT creation.
//
// At genesis a single UTXO is parked at the contract address carrying:
// - the category's entire FT supply (typically 10^18 units, just
// under the consensus cap 2^63-1)
// - a minting NFT (capability 0x02) — included so future
// upgrades/governance can issue new NFTs of the same category if
// desired; not used by mint() itself
//
// Each call to mint() transfers `tokenAmount` FT units from the
// reservoir to the caller in exchange for `tokenAmount * rate` sats of
// BCH. The reservoir UTXO is re-output to the contract with its FT
// amount reduced and the NFT preserved.
//
// Self-backed FT UTXOs:
// Each FT output to a user carries `tokenAmount * backingRateSats` BCH
// sats inside it. Redemption is therefore a normal P2PKH spend — the
// holder spends their FT UTXO and consensus releases the embedded BCH.
// No contract path needed for redeem. The protocol swap consumes a
// source-token FT UTXO this way and feeds the released BCH into the
// target-token contract's mint() in the same atomic transaction.
//
// Output layout (mint):
// [0] reservoir UTXO back to contract: minting NFT + (remaining_FT)
// at dust-or-greater value
// [1] FT to recipient, self-backed at tokenAmount * rate sats
// [2] founder fee, if feeSats > 0
// [3+] BCH change (unconstrained)
//
// Category byte order:
// The `tokenCategory` constructor parameter must be in CashTokens
// consensus order (little-endian = reverse of the display TXID).
// Libauth byte-reverses string `category` fields when encoding
// outputs, so the on-chain field is consensus order; the OP_*TOKEN-
// CATEGORY opcodes return consensus order; constructor bytes must
// match. The off-chain scripts handle the reversal.
//
// Capability propagation:
// tx.inputs[i].tokenCategory / tx.outputs[i].tokenCategory carry the
// 32-byte category id with the capability byte appended (0x02 for
// minting NFT) and no suffix for FT-only outputs. We propagate the
// reservoir's full tokenCategory to the reservoir-out output —
// consensus validates capability preservation. The bare 32-byte
// constructor `tokenCategory` parameter is compared only against the
// FT output, which carries no capability suffix.
//
// Spec divergence noted:
// Spec §4.3 illustrative sketch used a `tokenCapability` field (does
// not exist) and a "mint creates new FT supply" model (CashTokens
// forbids). This contract is the consensus-correct realisation. See
// snapshot 0008.
contract CoinMBA(
bytes tokenCategory, // bare 32-byte category id == genesis TXID
int backingRateSats, // sats per FT unit, >= 1000 per spec §1.3
int founderFeeBps, // 0..500 (0% .. 5%)
bytes founderLockingBytecode // 25-byte P2PKH bytecode of fee recipient
) {
// claim() — permissionless transfer-from-reservoir.
//
// Despite the historical "mint" naming in the CashTokens / spec
// literature, this function does NOT create new FT supply (that
// happened once at genesis and is consensus-fixed thereafter).
// It transfers `tokenAmount` FT units from the reservoir UTXO at
// this contract to `recipientLockingBytecode`, in exchange for
// `tokenAmount * backingRateSats` of BCH that the caller provides.
// Anyone holding BCH can call it.
function claim(int tokenAmount, bytes recipientLockingBytecode) {
require(tokenAmount > 0);
int grossSats = tokenAmount * backingRateSats;
int feeSats = grossSats * founderFeeBps / 10000;
// [0] reservoir back to this contract. Three invariants:
// - same lockingBytecode → stays at this contract address
// - same tokenCategory → preserves NFT category+capability
// - FT amount reduced by exactly tokenAmount (transferred to [1])
// Consensus enforces that the FT difference equals the amount
// appearing in [1]; we double-check by hand here so the contract
// can give a clear failure message rather than a generic token-
// validation error.
require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);
require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);
require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);
require(tx.outputs[0].value >= 546);
// [1] FT to recipient, self-backed.
// tokenCategory on FT outputs has no capability suffix — it is
// exactly the 32-byte category id we hold as the constructor
// parameter.
require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);
require(tx.outputs[1].tokenCategory == tokenCategory);
require(tx.outputs[1].tokenAmount == tokenAmount);
require(tx.outputs[1].value == grossSats);
// [2] Founder fee — only constrained when non-zero. If zero, the
// tx may have additional outputs (change, etc.) starting at
// index 2 with no contract restriction.
if (feeSats > 0) {
// Anti-dust: sub-dust fees are forbidden. Rounding noise must
// not produce non-standard outputs. Off-chain math must mirror.
require(feeSats >= 546);
require(tx.outputs[2].lockingBytecode == founderLockingBytecode);
require(tx.outputs[2].value == feeSats);
// Fee output is pure BCH — no token category.
require(tx.outputs[2].tokenCategory == 0x);
}
}
}