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>
This commit is contained in:
Local Dev 2026-10-09 00:00:15 +02:00
parent 6a58143966
commit 9a24a19c94
22 changed files with 203 additions and 400 deletions

View file

@ -1,117 +0,0 @@
pragma cashscript ^0.12.0;
// ArtemisionMintFT — the first covenant in the Artemision template family.
//
// Semantically IDENTICAL to DeviantOpenMintFT.cash at genesis: same
// reservoir model, same claim() invariants, same self-backed FT outputs,
// same 0..500 bps founder fee. Deployed as a SIBLING (not a replacement)
// so:
//
// • Every asset already deployed on chipnet using DeviantOpenMintFT.cash
// (ASM, ASM2, CAP, …) keeps working forever — assets are immutable at
// genesis (ADR-0054); the covenant that mints them cannot be swapped
// out without redeploying the asset itself (which would be a different
// category, i.e. a different asset).
// • The Artemision wizard uses THIS file for every new asset from now on,
// making the branding chain honest: "Artemision creates
// ArtemisionMintFT covenants which get listed in Katalogos."
// • This sibling is FREE TO DIVERGE. Version 1 mirrors DeviantOpenMintFT
// one-to-one; a v2 sibling can add a richer metadata slot or an
// alternative fee curve without disturbing anything already deployed.
//
// FAMILY (in this repo — see Parameters/Artemision/):
//
// ArtemisionMintFT.cash — this file. Static rate, open mint,
// fixed founder fee. Baseline for
// vanilla MBAs (ASM-shape).
// DeviantOracleMBA.cash — oracle-priced backing. Used by the
// BCHm family (USDm/EURm/…). Chipnet-
// validated in Parameters/ASM_Native/FIATm.
// DeviantShareMBA.cash — fee-split-at-consensus fork: each
// claim's fee splits into a company
// stream (companyLockingBytecode) +
// a shareholder stream
// (distributionLockingBytecode).
// Chipnet-proven (Parameters/ASM_Native/
// MBA_Shares/SESSION-RECORD_shares-
// products-chipnet.md).
// DistributionVault.cash — companion payout side for
// DeviantShareMBA. Oracle-attested
// (recipient+amount), replay-bound to
// the vault's own outpoint. Chipnet-
// proven.
// TermCouponVault.cash — DistributionVault + CLTV maturity
// sweep: t-bond variant. Chipnet-
// proven.
// DART / DEA / Elevation — dynamic-rate variants (designed,
// not deployed).
//
// Artemision presets that compose these:
// ① Rewards/loyalty coin — DeviantShareMBA · companySplitBps=10000
// (all fee to company; no shareholder
// stream; DistributionVault unused).
// ② Shares + dividends — DeviantShareMBA + DistributionVault.
// ③ T-bond — DeviantShareMBA + TermCouponVault.
//
// Artemision the WIZARD chooses among these templates based on the asset
// class the user picks; this file is what "static MBA" resolves to.
//
// Everything below the constructor is line-for-line the same as
// DeviantOpenMintFT.cash so a chipnet reader that already understands
// one can read the other. If a future v2 wants to diverge, it does so
// in a NEW file (ArtemisionMintFT_V2.cash) rather than editing this one —
// assets minted by this covenant address depend on this bytecode.
contract ArtemisionMintFT(
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])
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);
}
}
}

View file

@ -60,7 +60,7 @@ pragma cashscript ^0.12.0;
// forbids). This contract is the consensus-correct realisation. See
// snapshot 0008.
contract DeviantOpenMintFT(
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%)

34
contracts/DynamicMBA.cash Normal file
View file

@ -0,0 +1,34 @@
pragma cashscript ^0.12.0;
// DynamicMBA — scaffold reserved for Dynamic Rate Asset (DRA) covenant.
//
// DRA is a Mathematically Backed Asset whose per-unit backing rate is
// computed from on-chain state (reservoir depth, time, mint-count catalysts)
// rather than:
// - a constant (that's CoinMBA, templateId 0x00)
// - a signed feed (that's OracleMBA, templateId 0x02)
//
// Explicitly NOT FIATμ. FIATμ combines a dynamic-rate layer with an oracle
// signal to track a fiat purchasing-power target; DRA is the pure
// state-driven variant and is reflective against a shared pool rather than
// tracking an external price.
//
// Full design is intentionally deferred — the shared-pool reflective
// mechanics (see memory [[dra-reflective-fiatm-flagship]]) and the Odyssean
// maturity curve need a dedicated specification pass. This file reserves
// templateId 0x03 in the DVMB dispatch table and the artifact-name slot so
// nothing else lands under the DynamicMBA name by accident. It compiles,
// but it is not meant to be deployed.
// Minimum legal cashscript covenant — one trivial branch. Will be replaced
// in full when the DRA covenant lands.
contract DynamicMBA(
bytes32 tokenCategory,
) {
function placeholder() {
// Not deployable — rejects every call.
// Replace this body with the DRA state-machine when it lands.
require(1 == 2, 'DynamicMBA scaffold — covenant not yet implemented');
require(tokenCategory.length == 32);
}
}

View file

@ -29,7 +29,7 @@ pragma cashscript ^0.12.0;
// [2] founder fee (only if >= dust; sub-dust fees are WAIVED, not rejected)
// [3+] BCH change (unconstrained)
contract DeviantOracleMBA(
contract OracleMBA(
bytes tokenCategory, // bare 32-byte category id (consensus LE order)
pubkey oraclePk, // founder price-oracle public key
int founderFeeBps, // e.g. 5 = 0.05%, 500 = 5%

View file

@ -24,7 +24,7 @@ pragma cashscript ^0.12.0;
// cannot take or redirect it. (Payout from that covenant — oracle-attributed,
// time-weighted — is the next ADR-0042 step; this proves the COLLECTION split.)
contract DeviantShareMBA(
contract ShareMBA(
bytes tokenCategory, // bare 32-byte category id == genesis TXID
int backingRateSats, // sats per FT unit, >= 1000
int feeBps, // total fee, 0..500 (0% .. 5%)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,38 @@
{
"contractName": "DynamicMBA",
"constructorInputs": [
{
"name": "tokenCategory",
"type": "bytes32"
}
],
"abi": [
{
"name": "placeholder",
"inputs": []
}
],
"bytecode": "OP_1 OP_2 OP_NUMEQUALVERIFY OP_SIZE OP_NIP 20 OP_NUMEQUAL",
"source": "pragma cashscript ^0.12.0;\n\n// DynamicMBA — scaffold reserved for Dynamic Rate Asset (DRA) covenant.\n//\n// DRA is a Mathematically Backed Asset whose per-unit backing rate is\n// computed from on-chain state (reservoir depth, time, mint-count catalysts)\n// rather than:\n// - a constant (that's CoinMBA, templateId 0x00)\n// - a signed feed (that's OracleMBA, templateId 0x02)\n//\n// Explicitly NOT FIATμ. FIATμ combines a dynamic-rate layer with an oracle\n// signal to track a fiat purchasing-power target; DRA is the pure\n// state-driven variant and is reflective against a shared pool rather than\n// tracking an external price.\n//\n// Full design is intentionally deferred — the shared-pool reflective\n// mechanics (see memory [[dra-reflective-fiatm-flagship]]) and the Odyssean\n// maturity curve need a dedicated specification pass. This file reserves\n// templateId 0x03 in the DVMB dispatch table and the artifact-name slot so\n// nothing else lands under the DynamicMBA name by accident. It compiles,\n// but it is not meant to be deployed.\n\n// Minimum legal cashscript covenant — one trivial branch. Will be replaced\n// in full when the DRA covenant lands.\ncontract DynamicMBA(\n bytes32 tokenCategory,\n) {\n function placeholder() {\n // Not deployable — rejects every call.\n // Replace this body with the DRA state-machine when it lands.\n require(1 == 2, 'DynamicMBA scaffold — covenant not yet implemented');\n require(tokenCategory.length == 32);\n }\n}\n",
"debug": {
"bytecode": "51529d827701209c",
"sourceMap": "31:16:31:17;:21::22;:8::78:1;32:16:32:36;;:40::42:0;:8::44:1",
"logs": [],
"requires": [
{
"ip": 3,
"line": 31,
"message": "DynamicMBA scaffold — covenant not yet implemented"
},
{
"ip": 8,
"line": 32
}
]
},
"compiler": {
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-10-08T21:54:02.107Z"
}

View file

@ -1,5 +1,5 @@
{
"contractName": "DeviantOracleMBA",
"contractName": "OracleMBA",
"constructorInputs": [
{
"name": "tokenCategory",
@ -42,7 +42,7 @@
}
],
"bytecode": "OP_4 OP_PICK OP_0 OP_GREATERTHAN OP_VERIFY OP_6 OP_ROLL OP_7 OP_PICK OP_3 OP_ROLL OP_CHECKDATASIGVERIFY OP_5 OP_PICK OP_SIZE OP_NIP OP_16 OP_NUMEQUALVERIFY OP_5 OP_ROLL OP_12 OP_SPLIT OP_SWAP OP_SIZE OP_NIP OP_12 OP_NUMEQUALVERIFY OP_BIN2NUM OP_DUP OP_0 OP_GREATERTHAN OP_VERIFY OP_4 OP_PICK OP_SWAP OP_MUL OP_DUP OP_3 OP_ROLL OP_MUL 1027 OP_DIV OP_0 OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_INPUTINDEX OP_UTXOTOKENAMOUNT OP_6 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_0 OP_OUTPUTVALUE 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_1 OP_OUTPUTBYTECODE OP_6 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENAMOUNT OP_4 OP_ROLL OP_NUMEQUALVERIFY OP_1 OP_OUTPUTVALUE OP_ROT OP_NUMEQUALVERIFY OP_DUP 2202 OP_GREATERTHANOREQUAL OP_IF OP_2 OP_OUTPUTBYTECODE OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantOracleMBA — oracle-priced self-backed MBA (USD/EUR stablecoin shape).\n//\n// Sibling of DeviantOpenMintFT (fixed rate). Here the per-unit backing rate is\n// NOT a constructor constant — it is supplied by a founder-run price oracle at\n// claim time (checkDataSig). The FORWARD swap (BCH -> stablecoin) is oracle-\n// priced; the BACKWARD swap (stablecoin -> BCH) needs NO oracle and NO contract\n// path: each minted FT UTXO carries its mint-time BCH inside it, so redemption\n// is a plain P2PKH spend that releases exactly the locked BCH (self-backed).\n//\n// HONEST SEMANTICS: each unit is self-backed at its MINT-TIME price. A unit\n// minted at 1 USD = 10000 sats locks 10000; one minted at 12000 locks 12000.\n// So this is a \"dollar-worth-of-BCH-at-mint voucher, always redeemable for that\n// BCH\" — NOT a coin that keeps tracking USD after mint. That is the only\n// unilaterally-redeemable (MBA-shaped) stablecoin; a live USD tracker needs a\n// CDP/rebalancer and loses unilateral redemption.\n//\n// Oracle message (16 bytes, AnyHedge/Paryon format, all uint32 LE):\n// timestamp(4) || sequence(4) || contentSequence(4) || price(4)\n// price = sats per ATOMIC token unit (off-chain divides satsPerUSD by 10^dec).\n// Freshness (timestamp binding) is NOT enforced here — the known open limitation\n// (covenant verifies THAT the oracle signed, not WHEN). For production add a\n// CLTV/age bound on the timestamp field.\n//\n// Output layout (claim):\n// [0] reservoir back to contract: FT reduced, category/NFT preserved\n// [1] FT to recipient, self-backed at tokenAmount * price sats\n// [2] founder fee (only if >= dust; sub-dust fees are WAIVED, not rejected)\n// [3+] BCH change (unconstrained)\n\ncontract DeviantOracleMBA(\n bytes tokenCategory, // bare 32-byte category id (consensus LE order)\n pubkey oraclePk, // founder price-oracle public key\n int founderFeeBps, // e.g. 5 = 0.05%, 500 = 5%\n bytes founderLockingBytecode // 25-byte P2PKH of fee recipient\n) {\n function claim(\n int tokenAmount,\n bytes recipientLockingBytecode,\n datasig oracleSig,\n bytes oracleMessage\n ) {\n require(tokenAmount > 0);\n\n // --- oracle price attestation (forward swap only) ---\n require(checkDataSig(oracleSig, oracleMessage, oraclePk));\n require(oracleMessage.length == 16);\n bytes head, bytes priceBytes = oracleMessage.split(12); // last 4 bytes = price\n require(head.length == 12); // (also consumes `head`)\n int price = int(priceBytes);\n require(price > 0);\n\n int grossSats = tokenAmount * price;\n int feeSats = grossSats * founderFeeBps / 10000;\n\n // [0] reservoir back to this contract, FT reduced by tokenAmount.\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed at the oracle price (redeemable, no oracle).\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2] founder fee — enforced only when it clears dust; sub-dust waived.\n if (feeSats >= 546) {\n require(tx.outputs[2].lockingBytecode == founderLockingBytecode);\n require(tx.outputs[2].value == feeSats);\n require(tx.outputs[2].tokenCategory == 0x);\n }\n }\n}\n",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantOracleMBA — oracle-priced self-backed MBA (USD/EUR stablecoin shape).\n//\n// Sibling of DeviantOpenMintFT (fixed rate). Here the per-unit backing rate is\n// NOT a constructor constant — it is supplied by a founder-run price oracle at\n// claim time (checkDataSig). The FORWARD swap (BCH -> stablecoin) is oracle-\n// priced; the BACKWARD swap (stablecoin -> BCH) needs NO oracle and NO contract\n// path: each minted FT UTXO carries its mint-time BCH inside it, so redemption\n// is a plain P2PKH spend that releases exactly the locked BCH (self-backed).\n//\n// HONEST SEMANTICS: each unit is self-backed at its MINT-TIME price. A unit\n// minted at 1 USD = 10000 sats locks 10000; one minted at 12000 locks 12000.\n// So this is a \"dollar-worth-of-BCH-at-mint voucher, always redeemable for that\n// BCH\" — NOT a coin that keeps tracking USD after mint. That is the only\n// unilaterally-redeemable (MBA-shaped) stablecoin; a live USD tracker needs a\n// CDP/rebalancer and loses unilateral redemption.\n//\n// Oracle message (16 bytes, AnyHedge/Paryon format, all uint32 LE):\n// timestamp(4) || sequence(4) || contentSequence(4) || price(4)\n// price = sats per ATOMIC token unit (off-chain divides satsPerUSD by 10^dec).\n// Freshness (timestamp binding) is NOT enforced here — the known open limitation\n// (covenant verifies THAT the oracle signed, not WHEN). For production add a\n// CLTV/age bound on the timestamp field.\n//\n// Output layout (claim):\n// [0] reservoir back to contract: FT reduced, category/NFT preserved\n// [1] FT to recipient, self-backed at tokenAmount * price sats\n// [2] founder fee (only if >= dust; sub-dust fees are WAIVED, not rejected)\n// [3+] BCH change (unconstrained)\n\ncontract OracleMBA(\n bytes tokenCategory, // bare 32-byte category id (consensus LE order)\n pubkey oraclePk, // founder price-oracle public key\n int founderFeeBps, // e.g. 5 = 0.05%, 500 = 5%\n bytes founderLockingBytecode // 25-byte P2PKH of fee recipient\n) {\n function claim(\n int tokenAmount,\n bytes recipientLockingBytecode,\n datasig oracleSig,\n bytes oracleMessage\n ) {\n require(tokenAmount > 0);\n\n // --- oracle price attestation (forward swap only) ---\n require(checkDataSig(oracleSig, oracleMessage, oraclePk));\n require(oracleMessage.length == 16);\n bytes head, bytes priceBytes = oracleMessage.split(12); // last 4 bytes = price\n require(head.length == 12); // (also consumes `head`)\n int price = int(priceBytes);\n require(price > 0);\n\n int grossSats = tokenAmount * price;\n int feeSats = grossSats * founderFeeBps / 10000;\n\n // [0] reservoir back to this contract, FT reduced by tokenAmount.\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed at the oracle price (redeemable, no oracle).\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2] founder fee — enforced only when it clears dust; sub-dust waived.\n if (feeSats >= 546) {\n require(tx.outputs[2].lockingBytecode == founderLockingBytecode);\n require(tx.outputs[2].value == feeSats);\n require(tx.outputs[2].tokenCategory == 0x);\n }\n }\n}\n",
"debug": {
"bytecode": "547900a069567a5779537abb55798277609d557a5c7f7c82775c9d817600a06954797c9576537a950210279600cdc0c78800d1c0ce8800d3c0d05679949d00cc022202a26951cd567a8851d1537a8851d3547a9d51cc7b9d76022202a26352cd52798852cc789d52d10088686d51",
"sourceMap": "44:16:44:27;;:30::31;:16:::1;:8::33;47:29:47:38:0;;:40::53;;:55::63;;:8::66:1;48:16:48:29:0;;:::36:1;;:40::42:0;:8::44:1;49:39:49:52:0;;:59::61;:39::62:1;50:16:50:20:0;:::27:1;;:31::33:0;:8::35:1;51:20:51;52:16:52:21:0;:24::25;:16:::1;:8::27;54:24:54:35:0;;:38::43;:24:::1;55:22:55:31:0;:34::47;;:22:::1;:50::55:0;:22:::1;58:27:58:28:0;:16::45:1;:59::80:0;:49::97:1;:8::99;59:27:59:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;60:27:60:28:0;:16::41:1;:55::76:0;:45::89:1;:92::103:0;;:45:::1;:8::105;61:27:61:28:0;:16::35:1;:39::42:0;:16:::1;:8::44;64:27:64:28:0;:16::45:1;:49::73:0;;:8::75:1;65:27:65:28:0;:16::43:1;:47::60:0;;:8::62:1;66:27:66:28:0;:16::41:1;:45::56:0;;:8::58:1;67:27:67:28:0;:16::35:1;:39::48:0;:8::50:1;70:12:70:19:0;:23::26;:12:::1;:28:74:9:0;71:31:71:32;:20::49:1;:53::75:0;;:12::77:1;72:31:72:32:0;:20::39:1;:43::50:0;:12::52:1;73:31:73:32:0;:20::47:1;:51::53:0;:12::55:1;70:28:74:9;38:4:75:5;",
@ -118,5 +118,5 @@
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-09-06T16:25:52.645Z"
"updatedAt": "2026-10-08T21:54:01.371Z"
}

View file

@ -1,5 +1,5 @@
{
"contractName": "DeviantShareMBA",
"contractName": "ShareMBA",
"constructorInputs": [
{
"name": "tokenCategory",
@ -42,7 +42,7 @@
}
],
"bytecode": "OP_6 OP_PICK OP_0 OP_GREATERTHAN OP_VERIFY OP_6 OP_PICK OP_ROT OP_MUL OP_DUP OP_3 OP_ROLL OP_MUL 1027 OP_DIV OP_DUP OP_4 OP_ROLL OP_MUL 1027 OP_DIV OP_2DUP OP_SUB OP_0 OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_INPUTINDEX OP_UTXOTOKENAMOUNT OP_9 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_0 OP_OUTPUTVALUE 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_1 OP_OUTPUTBYTECODE OP_9 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_5 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENAMOUNT OP_7 OP_ROLL OP_NUMEQUALVERIFY OP_1 OP_OUTPUTVALUE OP_4 OP_ROLL OP_NUMEQUALVERIFY OP_ROT OP_0 OP_GREATERTHAN OP_IF OP_OVER 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_DUP 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTBYTECODE OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_PICK OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_4 OP_PICK OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_1",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantShareMBA — fork of DeviantOpenMintFT (ADR-0042, collection side).\n//\n// Splits the per-claim fee into TWO consensus-enforced streams instead of\n// one founder output:\n// - companySats -> companyLockingBytecode (the company treasury, paid directly)\n// - shareholderSats -> distributionLockingBytecode (the consensus-locked distribution\n// covenant — the holders' profit-share)\n//\n// fee = gross * feeBps / 10000\n// companySats = fee * companySplitBps / 10000 (company's share OF THE FEE)\n// shareholderSats = fee - companySats (routed to the distribution covenant)\n//\n// Output layout (claim):\n// [0] reservoir UTXO back to contract (minting NFT + remaining FT)\n// [1] FT to recipient, self-backed at tokenAmount * rate\n// [2] company fee -> companyLockingBytecode (companySats)\n// [3] shareholder share -> distributionLockingBytecode (shareholderSats)\n// [4+] BCH change (unconstrained)\n//\n// The shareholder stream never touches a company key: consensus routes it\n// straight to the distribution covenant's locking bytecode. The company\n// cannot take or redirect it. (Payout from that covenant — oracle-attributed,\n// time-weighted — is the next ADR-0042 step; this proves the COLLECTION split.)\n\ncontract DeviantShareMBA(\n bytes tokenCategory, // bare 32-byte category id == genesis TXID\n int backingRateSats, // sats per FT unit, >= 1000\n int feeBps, // total fee, 0..500 (0% .. 5%)\n int companySplitBps, // company's share of the fee, 0..10000\n bytes companyLockingBytecode, // company treasury (direct)\n bytes distributionLockingBytecode // consensus-locked distribution covenant\n) {\n function claim(int tokenAmount, bytes recipientLockingBytecode) {\n require(tokenAmount > 0);\n\n int grossSats = tokenAmount * backingRateSats;\n int feeSats = grossSats * feeBps / 10000;\n int companySats = feeSats * companySplitBps / 10000;\n int shareholderSats = feeSats - companySats;\n\n // [0] reservoir back to this contract (lockingBytecode + category + FT reduced).\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed.\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2]+[3] split fee. Both portions must clear dust when a fee is charged.\n // (Sub-dust waiver for tiny claims is a later refinement; the prototype\n // sizes claims so both sides clear 546.)\n if (feeSats > 0) {\n require(companySats >= 546);\n require(shareholderSats >= 546);\n\n // [2] company treasury — paid directly.\n require(tx.outputs[2].lockingBytecode == companyLockingBytecode);\n require(tx.outputs[2].value == companySats);\n require(tx.outputs[2].tokenCategory == 0x);\n\n // [3] shareholder profit-share — routed to the distribution covenant.\n require(tx.outputs[3].lockingBytecode == distributionLockingBytecode);\n require(tx.outputs[3].value == shareholderSats);\n require(tx.outputs[3].tokenCategory == 0x);\n }\n }\n}\n",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantShareMBA — fork of DeviantOpenMintFT (ADR-0042, collection side).\n//\n// Splits the per-claim fee into TWO consensus-enforced streams instead of\n// one founder output:\n// - companySats -> companyLockingBytecode (the company treasury, paid directly)\n// - shareholderSats -> distributionLockingBytecode (the consensus-locked distribution\n// covenant — the holders' profit-share)\n//\n// fee = gross * feeBps / 10000\n// companySats = fee * companySplitBps / 10000 (company's share OF THE FEE)\n// shareholderSats = fee - companySats (routed to the distribution covenant)\n//\n// Output layout (claim):\n// [0] reservoir UTXO back to contract (minting NFT + remaining FT)\n// [1] FT to recipient, self-backed at tokenAmount * rate\n// [2] company fee -> companyLockingBytecode (companySats)\n// [3] shareholder share -> distributionLockingBytecode (shareholderSats)\n// [4+] BCH change (unconstrained)\n//\n// The shareholder stream never touches a company key: consensus routes it\n// straight to the distribution covenant's locking bytecode. The company\n// cannot take or redirect it. (Payout from that covenant — oracle-attributed,\n// time-weighted — is the next ADR-0042 step; this proves the COLLECTION split.)\n\ncontract ShareMBA(\n bytes tokenCategory, // bare 32-byte category id == genesis TXID\n int backingRateSats, // sats per FT unit, >= 1000\n int feeBps, // total fee, 0..500 (0% .. 5%)\n int companySplitBps, // company's share of the fee, 0..10000\n bytes companyLockingBytecode, // company treasury (direct)\n bytes distributionLockingBytecode // consensus-locked distribution covenant\n) {\n function claim(int tokenAmount, bytes recipientLockingBytecode) {\n require(tokenAmount > 0);\n\n int grossSats = tokenAmount * backingRateSats;\n int feeSats = grossSats * feeBps / 10000;\n int companySats = feeSats * companySplitBps / 10000;\n int shareholderSats = feeSats - companySats;\n\n // [0] reservoir back to this contract (lockingBytecode + category + FT reduced).\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed.\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2]+[3] split fee. Both portions must clear dust when a fee is charged.\n // (Sub-dust waiver for tiny claims is a later refinement; the prototype\n // sizes claims so both sides clear 546.)\n if (feeSats > 0) {\n require(companySats >= 546);\n require(shareholderSats >= 546);\n\n // [2] company treasury — paid directly.\n require(tx.outputs[2].lockingBytecode == companyLockingBytecode);\n require(tx.outputs[2].value == companySats);\n require(tx.outputs[2].tokenCategory == 0x);\n\n // [3] shareholder profit-share — routed to the distribution covenant.\n require(tx.outputs[3].lockingBytecode == distributionLockingBytecode);\n require(tx.outputs[3].value == shareholderSats);\n require(tx.outputs[3].tokenCategory == 0x);\n }\n }\n}\n",
"debug": {
"bytecode": "567900a06956797b9576537a950210279676547a95021027966e9400cdc0c78800d1c0ce8800d3c0d05979949d00cc022202a26951cd597a8851d1557a8851d3577a9d51cc547a9d7b00a06378022202a26976022202a26952cd53798852cc52799d52d1008853cd54798853cc789d53d10088686d6d51",
"sourceMap": "36:16:36:27;;:30::31;:16:::1;:8::33;38:24:38:35:0;;:38::53;:24:::1;39:22:39:31:0;:34::40;;:22:::1;:43::48:0;:22:::1;40:26:40:33:0;:36::51;;:26:::1;:54::59:0;:26:::1;41:30:41:51:0;::::1;44:27:44:28:0;:16::45:1;:59::80:0;:49::97:1;:8::99;45:27:45:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;46:27:46:28:0;:16::41:1;:55::76:0;:45::89:1;:92::103:0;;:45:::1;:8::105;47:27:47:28:0;:16::35:1;:39::42:0;:16:::1;:8::44;50:27:50:28:0;:16::45:1;:49::73:0;;:8::75:1;51:27:51:28:0;:16::43:1;:47::60:0;;:8::62:1;52:27:52:28:0;:16::41:1;:45::56:0;;:8::58:1;53:27:53:28:0;:16::35:1;:39::48:0;;:8::50:1;58:12:58:19:0;:22::23;:12:::1;:25:71:9:0;59:20:59:31;:35::38;:20:::1;:12::40;60:20:60:35:0;:39::42;:20:::1;:12::44;63:31:63:32:0;:20::49:1;:53::75:0;;:12::77:1;64:31:64:32:0;:20::39:1;:43::54:0;;:12::56:1;65:31:65:32:0;:20::47:1;:51::53:0;:12::55:1;68:31:68:32:0;:20::49:1;:53::80:0;;:12::82:1;69:31:69:32:0;:20::39:1;:43::58:0;:12::60:1;70:31:70:32:0;:20::47:1;:51::53:0;:12::55:1;58:25:71:9;35:4:72:5;;",
@ -122,5 +122,5 @@
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-09-06T16:25:52.850Z"
"updatedAt": "2026-10-08T21:54:00.544Z"
}

View file

@ -16,8 +16,11 @@
// wizard's "Broadcast" button. Users pay only the miner fee (no founder
// fee on genesis; the founder fee applies to claim() outputs later).
import artifact from './artifacts/ArtemisionMintFT.json';
import shareArtifact from './artifacts/DeviantShareMBA.json';
// ArtemisionMintFT was a duplicate of DeviantOpenMintFT (same bytecode); the
// "Artemision" name lives on only as the wizard UI concept. Both genesis
// paths now use the clean CoinMBA + ShareMBA covenants directly.
import artifact from './artifacts/CoinMBA.json';
import shareArtifact from './artifacts/ShareMBA.json';
import type { Artifact } from 'cashscript';
import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
import type { Signer } from './signer-store';

View file

@ -22,7 +22,7 @@ import {
claimViaWcWithRetry, claimViaLocalSignerWithRetry, claimViaLocalSignerShareWithRetry,
} from './retry';
import { genesisArtemisionMBA, genesisDeviantShareMBA, loadLocalKnownMbas, clearAllLocalAssets, type GenesisResult, type ShareGenesisResult, type ShareGenesisParams } from './genesis';
import { publishViaLocalSigner, PublishError, TEMPLATE_ARTEMISION_MINT_FT, TEMPLATE_DEVIANT_SHARE_MBA, type PublishMetadata } from './publish';
import { publishViaLocalSigner, PublishError, TEMPLATE_COIN_MBA, TEMPLATE_SHARE_MBA, type PublishMetadata } from './publish';
import { uploadIcon, IconUploadError } from './icon-upload';
import { bchUsd, refreshBchUsd } from './pricing';
import { BCH_ICON_DATA_URI } from './icons';
@ -1759,7 +1759,7 @@ async function runRealPublish(sym: string): Promise<void> {
// distribution + split; Artemision (0x00 / undefined) publishes the
// founder LB. Common fields (ticker/name/icon) ride on top per DVMB.
const meta: PublishMetadata = mba.templateId === 0x01 ? {
templateId: TEMPLATE_DEVIANT_SHARE_MBA,
templateId: TEMPLATE_SHARE_MBA,
rateSats: mba.rateSats,
feeBps: mba.founderFeeBps, // slot reuse from Share's genesis stashing
companySplitBps: mba.companySplitBps,
@ -1770,7 +1770,7 @@ async function runRealPublish(sym: string): Promise<void> {
iconSha256Hex: iconOk ? assetRow?.iconSha256 : undefined,
iconPath: iconOk ? iconPath : undefined,
} : {
templateId: TEMPLATE_ARTEMISION_MINT_FT,
templateId: TEMPLATE_COIN_MBA,
rateSats: mba.rateSats,
founderFeeBps: mba.founderFeeBps,
founderLockingBytecodeHex: mba.founderLockingBytecodeHex,

View file

@ -13,8 +13,8 @@
// param order. Only difference is signer: script uses a WIF loaded from disk;
// mockup uses WC round-trip.
import artifact from './artifacts/DeviantOpenMintFT.json';
import shareArtifact from './artifacts/DeviantShareMBA.json';
import artifact from './artifacts/CoinMBA.json';
import shareArtifact from './artifacts/ShareMBA.json';
import type { Artifact } from 'cashscript';
import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
import { secp256k1, decodeCashAddress } from '@bitauth/libauth';

View file

@ -143,12 +143,13 @@ export class PublishError extends Error {
// 0x01 = DeviantShareMBA (later — needs additional fields)
// 0x02 = DeviantOracleMBA (later)
export const OP_RETURN_MAGIC = 'DVMB'; // 4-byte ASCII
export const TEMPLATE_ARTEMISION_MINT_FT = 0x00;
export const TEMPLATE_DEVIANT_SHARE_MBA = 0x01;
export const TEMPLATE_DEVIANT_ORACLE_MBA = 0x02;
export const TEMPLATE_COIN_MBA = 0x00;
export const TEMPLATE_SHARE_MBA = 0x01;
export const TEMPLATE_ORACLE_MBA = 0x02;
export const TEMPLATE_DYNAMIC_MBA = 0x03; // DRA scaffold — covenant not yet implemented
export interface PublishMetadata {
templateId: number; // TEMPLATE_ARTEMISION_MINT_FT | _SHARE_MBA | _ORACLE_MBA
templateId: number; // TEMPLATE_COIN_MBA | _SHARE_MBA | _ORACLE_MBA
// Common when template is ArtemisionMintFT / OpenMintFT
rateSats?: bigint; // sats per FT unit — Artemision + Share
founderFeeBps?: bigint; // 0..500 — Artemision + Oracle (Share uses feeBps)
@ -209,26 +210,26 @@ function encodeMetadataBlob(m: PublishMetadata): string {
// if a publisher hits the limit on mainnet, they drop the icon (still fine)
// or the name chunk. All chunks past chunk 1 are optional.
switch (m.templateId) {
case TEMPLATE_ARTEMISION_MINT_FT: {
case TEMPLATE_COIN_MBA: {
if (m.rateSats == null || m.founderFeeBps == null || !m.founderLockingBytecodeHex) {
throw new PublishError('ArtemisionMintFT DVMB needs rateSats + founderFeeBps + founderLockingBytecodeHex', 'other');
}
const buf = new Uint8Array(1 + 8 + 2 + 20);
const dv = new DataView(buf.buffer);
buf[0] = TEMPLATE_ARTEMISION_MINT_FT;
buf[0] = TEMPLATE_COIN_MBA;
writeU64LE(dv, 1, m.rateSats);
dv.setUint16(9, Number(m.founderFeeBps & 0xffffn), true);
buf.set(pkhFromLB(m.founderLockingBytecodeHex, 'founder'), 11);
return '0x' + Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
}
case TEMPLATE_DEVIANT_SHARE_MBA: {
case TEMPLATE_SHARE_MBA: {
if (m.rateSats == null || m.feeBps == null || m.companySplitBps == null ||
!m.companyLockingBytecodeHex || !m.distributionLockingBytecodeHex) {
throw new PublishError('DeviantShareMBA DVMB needs rate + fee + split + company/distribution LB', 'other');
}
const buf = new Uint8Array(1 + 8 + 2 + 2 + 20 + 20);
const dv = new DataView(buf.buffer);
buf[0] = TEMPLATE_DEVIANT_SHARE_MBA;
buf[0] = TEMPLATE_SHARE_MBA;
writeU64LE(dv, 1, m.rateSats);
dv.setUint16(9, Number(m.feeBps & 0xffffn), true);
dv.setUint16(11, Number(m.companySplitBps & 0xffffn), true);
@ -236,14 +237,14 @@ function encodeMetadataBlob(m: PublishMetadata): string {
buf.set(pkhFromLB(m.distributionLockingBytecodeHex, 'distribution'), 33);
return '0x' + Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
}
case TEMPLATE_DEVIANT_ORACLE_MBA: {
case TEMPLATE_ORACLE_MBA: {
if (m.founderFeeBps == null || !m.founderLockingBytecodeHex || !m.oraclePkHex) {
throw new PublishError('DeviantOracleMBA DVMB needs founderFeeBps + founderLockingBytecodeHex + oraclePkHex', 'other');
}
if (!/^[0-9a-f]{66}$/i.test(m.oraclePkHex)) throw new PublishError('oraclePkHex must be 66 hex chars (33-byte compressed)', 'other');
const buf = new Uint8Array(1 + 2 + 20 + 33);
const dv = new DataView(buf.buffer);
buf[0] = TEMPLATE_DEVIANT_ORACLE_MBA;
buf[0] = TEMPLATE_ORACLE_MBA;
dv.setUint16(1, Number(m.founderFeeBps & 0xffffn), true);
buf.set(pkhFromLB(m.founderLockingBytecodeHex, 'founder'), 3);
buf.set(hexToBin(m.oraclePkHex), 23);

View file

@ -47,13 +47,13 @@ export interface RegisteredEntry {
/** DVMB (Deviant Mint Bounds) — the OP_RETURN convention publish.ts writes
* alongside each register(). See publish.ts encodeMetadataBlob. */
const DVMB_MAGIC = new Uint8Array([0x44, 0x56, 0x4d, 0x42]); // "DVMB"
const TEMPLATE_ARTEMISION_MINT_FT = 0x00;
const TEMPLATE_DEVIANT_SHARE_MBA = 0x01;
const TEMPLATE_DEVIANT_ORACLE_MBA = 0x02;
const TEMPLATE_COIN_MBA = 0x00;
const TEMPLATE_SHARE_MBA = 0x01;
const TEMPLATE_ORACLE_MBA = 0x02;
export interface DiscoveredParams {
templateId: number;
// Common (ArtemisionMintFT + Share)
// Common (CoinMBA + Share)
rateSats?: bigint;
// Fee — meaning depends on template (Artemision + Oracle: founder fee;
// Share: total fee before split)
@ -110,19 +110,19 @@ export async function fetchEntryParams(
let companyPkh: Uint8Array | undefined;
let distributionPkh: Uint8Array | undefined;
let oraclePkHex: string | undefined;
if (templateId === TEMPLATE_ARTEMISION_MINT_FT) {
if (templateId === TEMPLATE_COIN_MBA) {
if (blob.length !== 1 + 8 + 2 + 20) { console.warn('[registry] Artemision DVMB size', blob.length); continue; }
rateSats = (BigInt(dv.getUint32(5, true)) << 32n) | BigInt(dv.getUint32(1, true));
feeBps = BigInt(dv.getUint16(9, true));
founderPkh = blob.slice(11, 31);
} else if (templateId === TEMPLATE_DEVIANT_SHARE_MBA) {
} else if (templateId === TEMPLATE_SHARE_MBA) {
if (blob.length !== 1 + 8 + 2 + 2 + 20 + 20) { console.warn('[registry] Share DVMB size', blob.length); continue; }
rateSats = (BigInt(dv.getUint32(5, true)) << 32n) | BigInt(dv.getUint32(1, true));
feeBps = BigInt(dv.getUint16(9, true));
companySplitBps = BigInt(dv.getUint16(11, true));
companyPkh = blob.slice(13, 33);
distributionPkh = blob.slice(33, 53);
} else if (templateId === TEMPLATE_DEVIANT_ORACLE_MBA) {
} else if (templateId === TEMPLATE_ORACLE_MBA) {
if (blob.length !== 1 + 2 + 20 + 33) { console.warn('[registry] Oracle DVMB size', blob.length); continue; }
feeBps = BigInt(dv.getUint16(1, true));
founderPkh = blob.slice(3, 23);
@ -276,13 +276,14 @@ function categoryToConsensusLE(hex: string): Uint8Array {
/** For an entry whose DVMB params were decoded, derive a KnownMba record
* that orbital.ts can trade against. Dispatches by templateId:
*
* 0x00 Artemision → ArtemisionMintFT contract address
* 0x01 DeviantShareMBA → DeviantShareMBA contract address (SWAP not yet
* wired in orbital.ts — the entry surfaces in the
* picker as view-only until claimShareMBA lands)
* 0x02 DeviantOracleMBA → DeviantOracleMBA contract address (same
* view-only caveat — oracle-priced claim path
* follows in a next pass)
* 0x00 CoinMBA → CoinMBA contract address (Static family)
* 0x01 ShareMBA → ShareMBA contract address (SWAP not yet wired in
* orbital.ts — the entry surfaces in the picker as
* view-only until claimShareMBA lands)
* 0x02 OracleMBA → OracleMBA contract address (same view-only caveat
* — oracle-priced claim path follows in a next pass)
* 0x03 DynamicMBA → DRA / dynamic-rate covenant (scaffold-only — not
* yet implemented; no claim path)
*
* The address derivation always runs so the resulting KnownMba can be
* used later without a re-derive; a Katalogos row that CAN'T claim yet
@ -296,10 +297,10 @@ export async function discoveredParamsToKnownMba(
const catLE = categoryToConsensusLE(entry.categoryId);
const displayTicker = params.ticker ?? ('K:' + entry.categoryId.slice(0, 6));
if (params.templateId === TEMPLATE_ARTEMISION_MINT_FT) {
if (params.templateId === TEMPLATE_COIN_MBA) {
if (!params.founderPkh || params.rateSats == null || params.feeBps == null) return null;
const founderLB = pkhToLB(params.founderPkh);
const artifact = (await import('./artifacts/ArtemisionMintFT.json')).default;
const artifact = (await import('./artifacts/CoinMBA.json')).default;
const contract = new Contract(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
artifact as any,
@ -318,12 +319,12 @@ export async function discoveredParamsToKnownMba(
};
}
if (params.templateId === TEMPLATE_DEVIANT_SHARE_MBA) {
if (params.templateId === TEMPLATE_SHARE_MBA) {
if (!params.companyPkh || !params.distributionPkh ||
params.rateSats == null || params.feeBps == null || params.companySplitBps == null) return null;
const companyLB = pkhToLB(params.companyPkh);
const distributionLB = pkhToLB(params.distributionPkh);
const artifact = (await import('./artifacts/DeviantShareMBA.json')).default;
const artifact = (await import('./artifacts/ShareMBA.json')).default;
const contract = new Contract(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
artifact as any,
@ -344,10 +345,10 @@ export async function discoveredParamsToKnownMba(
};
}
if (params.templateId === TEMPLATE_DEVIANT_ORACLE_MBA) {
if (params.templateId === TEMPLATE_ORACLE_MBA) {
if (!params.founderPkh || !params.oraclePkHex || params.feeBps == null) return null;
const founderLB = pkhToLB(params.founderPkh);
const artifact = (await import('./artifacts/DeviantOracleMBA.json')).default;
const artifact = (await import('./artifacts/OracleMBA.json')).default;
const contract = new Contract(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
artifact as any,

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,38 @@
{
"contractName": "DynamicMBA",
"constructorInputs": [
{
"name": "tokenCategory",
"type": "bytes32"
}
],
"abi": [
{
"name": "placeholder",
"inputs": []
}
],
"bytecode": "OP_1 OP_2 OP_NUMEQUALVERIFY OP_SIZE OP_NIP 20 OP_NUMEQUAL",
"source": "pragma cashscript ^0.12.0;\n\n// DynamicMBA — scaffold reserved for Dynamic Rate Asset (DRA) covenant.\n//\n// DRA is a Mathematically Backed Asset whose per-unit backing rate is\n// computed from on-chain state (reservoir depth, time, mint-count catalysts)\n// rather than:\n// - a constant (that's CoinMBA, templateId 0x00)\n// - a signed feed (that's OracleMBA, templateId 0x02)\n//\n// Explicitly NOT FIATμ. FIATμ combines a dynamic-rate layer with an oracle\n// signal to track a fiat purchasing-power target; DRA is the pure\n// state-driven variant and is reflective against a shared pool rather than\n// tracking an external price.\n//\n// Full design is intentionally deferred — the shared-pool reflective\n// mechanics (see memory [[dra-reflective-fiatm-flagship]]) and the Odyssean\n// maturity curve need a dedicated specification pass. This file reserves\n// templateId 0x03 in the DVMB dispatch table and the artifact-name slot so\n// nothing else lands under the DynamicMBA name by accident. It compiles,\n// but it is not meant to be deployed.\n\n// Minimum legal cashscript covenant — one trivial branch. Will be replaced\n// in full when the DRA covenant lands.\ncontract DynamicMBA(\n bytes32 tokenCategory,\n) {\n function placeholder() {\n // Not deployable — rejects every call.\n // Replace this body with the DRA state-machine when it lands.\n require(1 == 2, 'DynamicMBA scaffold — covenant not yet implemented');\n require(tokenCategory.length == 32);\n }\n}\n",
"debug": {
"bytecode": "51529d827701209c",
"sourceMap": "31:16:31:17;:21::22;:8::78:1;32:16:32:36;;:40::42:0;:8::44:1",
"logs": [],
"requires": [
{
"ip": 3,
"line": 31,
"message": "DynamicMBA scaffold — covenant not yet implemented"
},
{
"ip": 8,
"line": 32
}
]
},
"compiler": {
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-10-08T21:54:02.107Z"
}

View file

@ -1,5 +1,5 @@
{
"contractName": "DeviantOracleMBA",
"contractName": "OracleMBA",
"constructorInputs": [
{
"name": "tokenCategory",
@ -42,7 +42,7 @@
}
],
"bytecode": "OP_4 OP_PICK OP_0 OP_GREATERTHAN OP_VERIFY OP_6 OP_ROLL OP_7 OP_PICK OP_3 OP_ROLL OP_CHECKDATASIGVERIFY OP_5 OP_PICK OP_SIZE OP_NIP OP_16 OP_NUMEQUALVERIFY OP_5 OP_ROLL OP_12 OP_SPLIT OP_SWAP OP_SIZE OP_NIP OP_12 OP_NUMEQUALVERIFY OP_BIN2NUM OP_DUP OP_0 OP_GREATERTHAN OP_VERIFY OP_4 OP_PICK OP_SWAP OP_MUL OP_DUP OP_3 OP_ROLL OP_MUL 1027 OP_DIV OP_0 OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_INPUTINDEX OP_UTXOTOKENAMOUNT OP_6 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_0 OP_OUTPUTVALUE 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_1 OP_OUTPUTBYTECODE OP_6 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENAMOUNT OP_4 OP_ROLL OP_NUMEQUALVERIFY OP_1 OP_OUTPUTVALUE OP_ROT OP_NUMEQUALVERIFY OP_DUP 2202 OP_GREATERTHANOREQUAL OP_IF OP_2 OP_OUTPUTBYTECODE OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantOracleMBA — oracle-priced self-backed MBA (USD/EUR stablecoin shape).\n//\n// Sibling of DeviantOpenMintFT (fixed rate). Here the per-unit backing rate is\n// NOT a constructor constant — it is supplied by a founder-run price oracle at\n// claim time (checkDataSig). The FORWARD swap (BCH -> stablecoin) is oracle-\n// priced; the BACKWARD swap (stablecoin -> BCH) needs NO oracle and NO contract\n// path: each minted FT UTXO carries its mint-time BCH inside it, so redemption\n// is a plain P2PKH spend that releases exactly the locked BCH (self-backed).\n//\n// HONEST SEMANTICS: each unit is self-backed at its MINT-TIME price. A unit\n// minted at 1 USD = 10000 sats locks 10000; one minted at 12000 locks 12000.\n// So this is a \"dollar-worth-of-BCH-at-mint voucher, always redeemable for that\n// BCH\" — NOT a coin that keeps tracking USD after mint. That is the only\n// unilaterally-redeemable (MBA-shaped) stablecoin; a live USD tracker needs a\n// CDP/rebalancer and loses unilateral redemption.\n//\n// Oracle message (16 bytes, AnyHedge/Paryon format, all uint32 LE):\n// timestamp(4) || sequence(4) || contentSequence(4) || price(4)\n// price = sats per ATOMIC token unit (off-chain divides satsPerUSD by 10^dec).\n// Freshness (timestamp binding) is NOT enforced here — the known open limitation\n// (covenant verifies THAT the oracle signed, not WHEN). For production add a\n// CLTV/age bound on the timestamp field.\n//\n// Output layout (claim):\n// [0] reservoir back to contract: FT reduced, category/NFT preserved\n// [1] FT to recipient, self-backed at tokenAmount * price sats\n// [2] founder fee (only if >= dust; sub-dust fees are WAIVED, not rejected)\n// [3+] BCH change (unconstrained)\n\ncontract DeviantOracleMBA(\n bytes tokenCategory, // bare 32-byte category id (consensus LE order)\n pubkey oraclePk, // founder price-oracle public key\n int founderFeeBps, // e.g. 5 = 0.05%, 500 = 5%\n bytes founderLockingBytecode // 25-byte P2PKH of fee recipient\n) {\n function claim(\n int tokenAmount,\n bytes recipientLockingBytecode,\n datasig oracleSig,\n bytes oracleMessage\n ) {\n require(tokenAmount > 0);\n\n // --- oracle price attestation (forward swap only) ---\n require(checkDataSig(oracleSig, oracleMessage, oraclePk));\n require(oracleMessage.length == 16);\n bytes head, bytes priceBytes = oracleMessage.split(12); // last 4 bytes = price\n require(head.length == 12); // (also consumes `head`)\n int price = int(priceBytes);\n require(price > 0);\n\n int grossSats = tokenAmount * price;\n int feeSats = grossSats * founderFeeBps / 10000;\n\n // [0] reservoir back to this contract, FT reduced by tokenAmount.\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed at the oracle price (redeemable, no oracle).\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2] founder fee — enforced only when it clears dust; sub-dust waived.\n if (feeSats >= 546) {\n require(tx.outputs[2].lockingBytecode == founderLockingBytecode);\n require(tx.outputs[2].value == feeSats);\n require(tx.outputs[2].tokenCategory == 0x);\n }\n }\n}\n",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantOracleMBA — oracle-priced self-backed MBA (USD/EUR stablecoin shape).\n//\n// Sibling of DeviantOpenMintFT (fixed rate). Here the per-unit backing rate is\n// NOT a constructor constant — it is supplied by a founder-run price oracle at\n// claim time (checkDataSig). The FORWARD swap (BCH -> stablecoin) is oracle-\n// priced; the BACKWARD swap (stablecoin -> BCH) needs NO oracle and NO contract\n// path: each minted FT UTXO carries its mint-time BCH inside it, so redemption\n// is a plain P2PKH spend that releases exactly the locked BCH (self-backed).\n//\n// HONEST SEMANTICS: each unit is self-backed at its MINT-TIME price. A unit\n// minted at 1 USD = 10000 sats locks 10000; one minted at 12000 locks 12000.\n// So this is a \"dollar-worth-of-BCH-at-mint voucher, always redeemable for that\n// BCH\" — NOT a coin that keeps tracking USD after mint. That is the only\n// unilaterally-redeemable (MBA-shaped) stablecoin; a live USD tracker needs a\n// CDP/rebalancer and loses unilateral redemption.\n//\n// Oracle message (16 bytes, AnyHedge/Paryon format, all uint32 LE):\n// timestamp(4) || sequence(4) || contentSequence(4) || price(4)\n// price = sats per ATOMIC token unit (off-chain divides satsPerUSD by 10^dec).\n// Freshness (timestamp binding) is NOT enforced here — the known open limitation\n// (covenant verifies THAT the oracle signed, not WHEN). For production add a\n// CLTV/age bound on the timestamp field.\n//\n// Output layout (claim):\n// [0] reservoir back to contract: FT reduced, category/NFT preserved\n// [1] FT to recipient, self-backed at tokenAmount * price sats\n// [2] founder fee (only if >= dust; sub-dust fees are WAIVED, not rejected)\n// [3+] BCH change (unconstrained)\n\ncontract OracleMBA(\n bytes tokenCategory, // bare 32-byte category id (consensus LE order)\n pubkey oraclePk, // founder price-oracle public key\n int founderFeeBps, // e.g. 5 = 0.05%, 500 = 5%\n bytes founderLockingBytecode // 25-byte P2PKH of fee recipient\n) {\n function claim(\n int tokenAmount,\n bytes recipientLockingBytecode,\n datasig oracleSig,\n bytes oracleMessage\n ) {\n require(tokenAmount > 0);\n\n // --- oracle price attestation (forward swap only) ---\n require(checkDataSig(oracleSig, oracleMessage, oraclePk));\n require(oracleMessage.length == 16);\n bytes head, bytes priceBytes = oracleMessage.split(12); // last 4 bytes = price\n require(head.length == 12); // (also consumes `head`)\n int price = int(priceBytes);\n require(price > 0);\n\n int grossSats = tokenAmount * price;\n int feeSats = grossSats * founderFeeBps / 10000;\n\n // [0] reservoir back to this contract, FT reduced by tokenAmount.\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed at the oracle price (redeemable, no oracle).\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2] founder fee — enforced only when it clears dust; sub-dust waived.\n if (feeSats >= 546) {\n require(tx.outputs[2].lockingBytecode == founderLockingBytecode);\n require(tx.outputs[2].value == feeSats);\n require(tx.outputs[2].tokenCategory == 0x);\n }\n }\n}\n",
"debug": {
"bytecode": "547900a069567a5779537abb55798277609d557a5c7f7c82775c9d817600a06954797c9576537a950210279600cdc0c78800d1c0ce8800d3c0d05679949d00cc022202a26951cd567a8851d1537a8851d3547a9d51cc7b9d76022202a26352cd52798852cc789d52d10088686d51",
"sourceMap": "44:16:44:27;;:30::31;:16:::1;:8::33;47:29:47:38:0;;:40::53;;:55::63;;:8::66:1;48:16:48:29:0;;:::36:1;;:40::42:0;:8::44:1;49:39:49:52:0;;:59::61;:39::62:1;50:16:50:20:0;:::27:1;;:31::33:0;:8::35:1;51:20:51;52:16:52:21:0;:24::25;:16:::1;:8::27;54:24:54:35:0;;:38::43;:24:::1;55:22:55:31:0;:34::47;;:22:::1;:50::55:0;:22:::1;58:27:58:28:0;:16::45:1;:59::80:0;:49::97:1;:8::99;59:27:59:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;60:27:60:28:0;:16::41:1;:55::76:0;:45::89:1;:92::103:0;;:45:::1;:8::105;61:27:61:28:0;:16::35:1;:39::42:0;:16:::1;:8::44;64:27:64:28:0;:16::45:1;:49::73:0;;:8::75:1;65:27:65:28:0;:16::43:1;:47::60:0;;:8::62:1;66:27:66:28:0;:16::41:1;:45::56:0;;:8::58:1;67:27:67:28:0;:16::35:1;:39::48:0;:8::50:1;70:12:70:19:0;:23::26;:12:::1;:28:74:9:0;71:31:71:32;:20::49:1;:53::75:0;;:12::77:1;72:31:72:32:0;:20::39:1;:43::50:0;:12::52:1;73:31:73:32:0;:20::47:1;:51::53:0;:12::55:1;70:28:74:9;38:4:75:5;",
@ -118,5 +118,5 @@
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-09-06T16:25:52.645Z"
"updatedAt": "2026-10-08T21:54:01.371Z"
}

View file

@ -1,5 +1,5 @@
{
"contractName": "DeviantShareMBA",
"contractName": "ShareMBA",
"constructorInputs": [
{
"name": "tokenCategory",
@ -42,7 +42,7 @@
}
],
"bytecode": "OP_6 OP_PICK OP_0 OP_GREATERTHAN OP_VERIFY OP_6 OP_PICK OP_ROT OP_MUL OP_DUP OP_3 OP_ROLL OP_MUL 1027 OP_DIV OP_DUP OP_4 OP_ROLL OP_MUL 1027 OP_DIV OP_2DUP OP_SUB OP_0 OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_INPUTINDEX OP_UTXOTOKENAMOUNT OP_9 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_0 OP_OUTPUTVALUE 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_1 OP_OUTPUTBYTECODE OP_9 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_5 OP_ROLL OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENAMOUNT OP_7 OP_ROLL OP_NUMEQUALVERIFY OP_1 OP_OUTPUTVALUE OP_4 OP_ROLL OP_NUMEQUALVERIFY OP_ROT OP_0 OP_GREATERTHAN OP_IF OP_OVER 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_DUP 2202 OP_GREATERTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTBYTECODE OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_PICK OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_4 OP_PICK OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_1",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantShareMBA — fork of DeviantOpenMintFT (ADR-0042, collection side).\n//\n// Splits the per-claim fee into TWO consensus-enforced streams instead of\n// one founder output:\n// - companySats -> companyLockingBytecode (the company treasury, paid directly)\n// - shareholderSats -> distributionLockingBytecode (the consensus-locked distribution\n// covenant — the holders' profit-share)\n//\n// fee = gross * feeBps / 10000\n// companySats = fee * companySplitBps / 10000 (company's share OF THE FEE)\n// shareholderSats = fee - companySats (routed to the distribution covenant)\n//\n// Output layout (claim):\n// [0] reservoir UTXO back to contract (minting NFT + remaining FT)\n// [1] FT to recipient, self-backed at tokenAmount * rate\n// [2] company fee -> companyLockingBytecode (companySats)\n// [3] shareholder share -> distributionLockingBytecode (shareholderSats)\n// [4+] BCH change (unconstrained)\n//\n// The shareholder stream never touches a company key: consensus routes it\n// straight to the distribution covenant's locking bytecode. The company\n// cannot take or redirect it. (Payout from that covenant — oracle-attributed,\n// time-weighted — is the next ADR-0042 step; this proves the COLLECTION split.)\n\ncontract DeviantShareMBA(\n bytes tokenCategory, // bare 32-byte category id == genesis TXID\n int backingRateSats, // sats per FT unit, >= 1000\n int feeBps, // total fee, 0..500 (0% .. 5%)\n int companySplitBps, // company's share of the fee, 0..10000\n bytes companyLockingBytecode, // company treasury (direct)\n bytes distributionLockingBytecode // consensus-locked distribution covenant\n) {\n function claim(int tokenAmount, bytes recipientLockingBytecode) {\n require(tokenAmount > 0);\n\n int grossSats = tokenAmount * backingRateSats;\n int feeSats = grossSats * feeBps / 10000;\n int companySats = feeSats * companySplitBps / 10000;\n int shareholderSats = feeSats - companySats;\n\n // [0] reservoir back to this contract (lockingBytecode + category + FT reduced).\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed.\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2]+[3] split fee. Both portions must clear dust when a fee is charged.\n // (Sub-dust waiver for tiny claims is a later refinement; the prototype\n // sizes claims so both sides clear 546.)\n if (feeSats > 0) {\n require(companySats >= 546);\n require(shareholderSats >= 546);\n\n // [2] company treasury — paid directly.\n require(tx.outputs[2].lockingBytecode == companyLockingBytecode);\n require(tx.outputs[2].value == companySats);\n require(tx.outputs[2].tokenCategory == 0x);\n\n // [3] shareholder profit-share — routed to the distribution covenant.\n require(tx.outputs[3].lockingBytecode == distributionLockingBytecode);\n require(tx.outputs[3].value == shareholderSats);\n require(tx.outputs[3].tokenCategory == 0x);\n }\n }\n}\n",
"source": "pragma cashscript ^0.12.0;\n\n// DeviantShareMBA — fork of DeviantOpenMintFT (ADR-0042, collection side).\n//\n// Splits the per-claim fee into TWO consensus-enforced streams instead of\n// one founder output:\n// - companySats -> companyLockingBytecode (the company treasury, paid directly)\n// - shareholderSats -> distributionLockingBytecode (the consensus-locked distribution\n// covenant — the holders' profit-share)\n//\n// fee = gross * feeBps / 10000\n// companySats = fee * companySplitBps / 10000 (company's share OF THE FEE)\n// shareholderSats = fee - companySats (routed to the distribution covenant)\n//\n// Output layout (claim):\n// [0] reservoir UTXO back to contract (minting NFT + remaining FT)\n// [1] FT to recipient, self-backed at tokenAmount * rate\n// [2] company fee -> companyLockingBytecode (companySats)\n// [3] shareholder share -> distributionLockingBytecode (shareholderSats)\n// [4+] BCH change (unconstrained)\n//\n// The shareholder stream never touches a company key: consensus routes it\n// straight to the distribution covenant's locking bytecode. The company\n// cannot take or redirect it. (Payout from that covenant — oracle-attributed,\n// time-weighted — is the next ADR-0042 step; this proves the COLLECTION split.)\n\ncontract ShareMBA(\n bytes tokenCategory, // bare 32-byte category id == genesis TXID\n int backingRateSats, // sats per FT unit, >= 1000\n int feeBps, // total fee, 0..500 (0% .. 5%)\n int companySplitBps, // company's share of the fee, 0..10000\n bytes companyLockingBytecode, // company treasury (direct)\n bytes distributionLockingBytecode // consensus-locked distribution covenant\n) {\n function claim(int tokenAmount, bytes recipientLockingBytecode) {\n require(tokenAmount > 0);\n\n int grossSats = tokenAmount * backingRateSats;\n int feeSats = grossSats * feeBps / 10000;\n int companySats = feeSats * companySplitBps / 10000;\n int shareholderSats = feeSats - companySats;\n\n // [0] reservoir back to this contract (lockingBytecode + category + FT reduced).\n require(tx.outputs[0].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\n require(tx.outputs[0].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[0].tokenAmount == tx.inputs[this.activeInputIndex].tokenAmount - tokenAmount);\n require(tx.outputs[0].value >= 546);\n\n // [1] FT to recipient, self-backed.\n require(tx.outputs[1].lockingBytecode == recipientLockingBytecode);\n require(tx.outputs[1].tokenCategory == tokenCategory);\n require(tx.outputs[1].tokenAmount == tokenAmount);\n require(tx.outputs[1].value == grossSats);\n\n // [2]+[3] split fee. Both portions must clear dust when a fee is charged.\n // (Sub-dust waiver for tiny claims is a later refinement; the prototype\n // sizes claims so both sides clear 546.)\n if (feeSats > 0) {\n require(companySats >= 546);\n require(shareholderSats >= 546);\n\n // [2] company treasury — paid directly.\n require(tx.outputs[2].lockingBytecode == companyLockingBytecode);\n require(tx.outputs[2].value == companySats);\n require(tx.outputs[2].tokenCategory == 0x);\n\n // [3] shareholder profit-share — routed to the distribution covenant.\n require(tx.outputs[3].lockingBytecode == distributionLockingBytecode);\n require(tx.outputs[3].value == shareholderSats);\n require(tx.outputs[3].tokenCategory == 0x);\n }\n }\n}\n",
"debug": {
"bytecode": "567900a06956797b9576537a950210279676547a95021027966e9400cdc0c78800d1c0ce8800d3c0d05979949d00cc022202a26951cd597a8851d1557a8851d3577a9d51cc547a9d7b00a06378022202a26976022202a26952cd53798852cc52799d52d1008853cd54798853cc789d53d10088686d6d51",
"sourceMap": "36:16:36:27;;:30::31;:16:::1;:8::33;38:24:38:35:0;;:38::53;:24:::1;39:22:39:31:0;:34::40;;:22:::1;:43::48:0;:22:::1;40:26:40:33:0;:36::51;;:26:::1;:54::59:0;:26:::1;41:30:41:51:0;::::1;44:27:44:28:0;:16::45:1;:59::80:0;:49::97:1;:8::99;45:27:45:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;46:27:46:28:0;:16::41:1;:55::76:0;:45::89:1;:92::103:0;;:45:::1;:8::105;47:27:47:28:0;:16::35:1;:39::42:0;:16:::1;:8::44;50:27:50:28:0;:16::45:1;:49::73:0;;:8::75:1;51:27:51:28:0;:16::43:1;:47::60:0;;:8::62:1;52:27:52:28:0;:16::41:1;:45::56:0;;:8::58:1;53:27:53:28:0;:16::35:1;:39::48:0;;:8::50:1;58:12:58:19:0;:22::23;:12:::1;:25:71:9:0;59:20:59:31;:35::38;:20:::1;:12::40;60:20:60:35:0;:39::42;:20:::1;:12::44;63:31:63:32:0;:20::49:1;:53::75:0;;:12::77:1;64:31:64:32:0;:20::39:1;:43::54:0;;:12::56:1;65:31:65:32:0;:20::47:1;:51::53:0;:12::55:1;68:31:68:32:0;:20::49:1;:53::80:0;;:12::82:1;69:31:69:32:0;:20::39:1;:43::58:0;:12::60:1;70:31:70:32:0;:20::47:1;:51::53:0;:12::55:1;58:25:71:9;35:4:72:5;;",
@ -122,5 +122,5 @@
"name": "cashc",
"version": "0.12.1"
},
"updatedAt": "2026-09-06T16:25:52.850Z"
"updatedAt": "2026-10-08T21:54:00.544Z"
}

View file

@ -43,37 +43,38 @@
<ul class="artifact-list">
<li>
<h3>DeviantOpenMintFT.json</h3>
<h3>CoinMBA.json</h3>
<p class="template-id">templateId <code>0x00</code> · Static family</p>
<p>The default MBA covenant. Fixed sats/unit rate baked at genesis, flat <code>founderFeeBps</code> (0..500). Reservoir carries the minting NFT and the circulating supply; every claim consumes the current reservoir and emits a fresh one with updated supply.</p>
<p>The default MBA covenant. Fixed sats/unit rate baked at genesis, flat <code>founderFeeBps</code> (0..500). Reservoir carries the minting NFT and the circulating supply; every claim consumes the current reservoir and emits a fresh one with updated supply. The sound-money archetype of the family — "coin" is literal: a measured amount of BCH wrapped in a covenant stamp.</p>
<p><strong>Constructor args</strong>: <code>[tokenCategory (bytes32), rateSats (int), feeBps (int), founderLockingBytecode (bytes25)]</code></p>
<a class="download" href="./DeviantOpenMintFT.json" download>Download JSON</a>
<a class="download" href="./CoinMBA.json" download>Download JSON</a>
</li>
<li>
<h3>ArtemisionMintFT.json</h3>
<p class="template-id">templateId <code>0x00</code> · Static family (genesis template)</p>
<p>Equivalent shape to DeviantOpenMintFT — used by the Artemision wizard to deploy fresh MBAs. The two artifacts have the same bytecode in practice; the separate file is kept for provenance and compiler-version pinning.</p>
<a class="download" href="./ArtemisionMintFT.json" download>Download JSON</a>
</li>
<li>
<h3>DeviantShareMBA.json</h3>
<h3>ShareMBA.json</h3>
<p class="template-id">templateId <code>0x01</code> · Share family</p>
<p>Flat <code>feeBps</code>, split by <code>companySplitBps</code> between a company treasury output and a shareholder distribution vault output. Each claim produces exactly four non-change outputs: new reservoir, user's MBA, company share, distribution share.</p>
<p><strong>Constructor args</strong>: <code>[tokenCategory, rateSats, feeBps, companySplitBps, companyLockingBytecode (bytes25), distributionLockingBytecode (bytes25)]</code></p>
<a class="download" href="./DeviantShareMBA.json" download>Download JSON</a>
<a class="download" href="./ShareMBA.json" download>Download JSON</a>
</li>
<li>
<h3>DeviantOracleMBA.json</h3>
<h3>OracleMBA.json</h3>
<p class="template-id">templateId <code>0x02</code> · Oracle family</p>
<p>Rate set by a signed AnyHedge-format price feed at claim time (<code>OP_CHECKDATASIG</code>). Flat <code>feeBps</code> on top. Oracle pubkey is baked at genesis; changing it requires a new covenant deployment.</p>
<p><strong>Constructor args</strong>: <code>[tokenCategory, feeBps, founderLockingBytecode, oraclePubKey (bytes33)]</code></p>
<a class="download" href="./DeviantOracleMBA.json" download>Download JSON</a>
<a class="download" href="./OracleMBA.json" download>Download JSON</a>
</li>
<li>
<h3>DynamicMBA.json <span class="template-id" style="display:inline;color:var(--muted)">(scaffold)</span></h3>
<p class="template-id">templateId <code>0x03</code> · Dynamic Rate family · <em>not yet deployable</em></p>
<p>Reserved for the Dynamic Rate Asset (DRA) covenant — a pure state-driven rate (reservoir depth, time, mint-count catalysts), no oracle. Distinct from FIATμ, which combines a dynamic-rate layer with an oracle signal to track a fiat purchasing-power target; DRA is the pure state-driven variant and is reflective against a shared pool. The current artifact is a scaffold stub (rejects every call); it reserves the templateId slot and the artifact filename so nothing else lands under this name by accident.</p>
<p><strong>Constructor args</strong>: <code>[tokenCategory (bytes32)]</code> <em>(placeholder — real signature arrives with the DRA spec)</em></p>
<a class="download" href="./DynamicMBA.json" download>Download JSON (scaffold)</a>
</li>
</ul>
<h2>Example — construct and verify</h2>
<pre>import { Contract, ElectrumNetworkProvider } from 'cashscript';
import artifact from './DeviantOpenMintFT.json';
import artifact from './CoinMBA.json';
// Params read from the DVMB OP_RETURN of the asset's genesis (or Katalogos entry).
const categoryId = '09af8a5b2fc2c9a2449045a8d5a6aa60db493989682eca257a57fa8d75d67231';

View file

@ -85,8 +85,8 @@
We do not call a backed asset a "token" — "token" in this project means an unbacked CashToken (ASM, GMX, etc. that stand on their own economics). The on-chain backing is the deliberate differentiator; "MBA" is the only name we use for a backed one. The raw chain primitive is "CashToken FT" but the product is a backed asset.
</aside>
<h2 id="families">Three template families</h2>
<p>Every MBA you encounter uses one of three cashscript covenants. The one you're reading is identifiable from the <code>templateId</code> byte in the asset's DVMB OP_RETURN sidecar, or from the covenant bytecode itself.</p>
<h2 id="families">Template families</h2>
<p>Every MBA uses one of four cashscript covenants. The one you're reading is identifiable from the <code>templateId</code> byte in the asset's DVMB OP_RETURN sidecar, or from the covenant bytecode itself. Three families are live (CoinMBA, ShareMBA, OracleMBA); the fourth (DynamicMBA) is a reserved slot for the Dynamic Rate Asset covenant.</p>
<table>
<thead>
<tr><th>templateId</th><th>Family</th><th>Rate</th><th>Fee behavior</th><th>Who gets paid</th></tr>
@ -94,27 +94,35 @@
<tbody>
<tr>
<td class="mono">0x00</td>
<td>Static · <code>DeviantOpenMintFT</code> / <code>ArtemisionMintFT</code></td>
<td>Static · <code>CoinMBA</code></td>
<td>Fixed sats/unit, baked at genesis</td>
<td>Flat <code>founderFeeBps</code> (0..500)</td>
<td>Founder of the asset</td>
</tr>
<tr>
<td class="mono">0x01</td>
<td>Share · <code>DeviantShareMBA</code></td>
<td>Share · <code>ShareMBA</code></td>
<td>Fixed sats/unit</td>
<td>Flat <code>feeBps</code>, split <code>companySplitBps</code>:(10000-split) between company and shareholder vault</td>
<td>Company treasury + distribution vault</td>
</tr>
<tr>
<td class="mono">0x02</td>
<td>Oracle · <code>DeviantOracleMBA</code></td>
<td>Oracle · <code>OracleMBA</code></td>
<td>Set by a signed price feed at claim time</td>
<td>Flat <code>feeBps</code> + oracle-sized overhead</td>
<td>Founder; oracle receives no fee from this flow</td>
</tr>
<tr>
<td class="mono">0x03</td>
<td>Dynamic · <code>DynamicMBA</code> <em>(scaffold)</em></td>
<td>Computed from on-chain state (reservoir depth, time, mint-count catalysts) — no oracle</td>
<td>TBD with the DRA spec</td>
<td>TBD</td>
</tr>
</tbody>
</table>
<p><em>DynamicMBA is distinct from FIATμ: FIATμ combines a dynamic-rate layer with an oracle signal to track a fiat purchasing-power target, while pure DRA (what DynamicMBA covers) works without any oracle, reflective against a shared pool.</em></p>
<p>The protocol-level fee behavior is the same across all three: on top of the covenant's own fee, a <strong>platform fee of 0.5%</strong> currently applies (40% to the Potidaea platform, 60% to the DEA Yield vault), configured in the client at swap-build time. The platform fee is a client enforcement, not a covenant rule — a wallet that strips it still produces a tx the covenant accepts, so trust in that fee rests on the client, not the chain.</p>
<h2 id="interacting">How to interact</h2>
@ -163,7 +171,7 @@
<p>Needed if the wallet wants to let the user mint or redeem an MBA without routing them to the Potidaea dApp. Requires the covenant artifact plus the exact transaction shape.</p>
<p><strong>What you need:</strong></p>
<ul>
<li><strong>The covenant artifact JSON</strong> — all four templates published at <a href="./artifacts/">/docs/mba/artifacts/</a> (Static, Share, Oracle, plus the Artemision genesis variant). Pass the right one to <code>new Contract(artifact, [...args])</code>. The artifacts page also carries the constructor argument order and a verification example you can copy.</li>
<li><strong>The covenant artifact JSON</strong> — the three live templates (CoinMBA, ShareMBA, OracleMBA) and the DynamicMBA scaffold are published at <a href="./artifacts/">/docs/mba/artifacts/</a>. Pass the right one to <code>new Contract(artifact, [...args])</code>. The artifacts page carries the constructor argument order and a verification example you can copy.</li>
<li><strong>Constructor args</strong>, in order: <code>[tokenCategory: bytes32, backingRateSats: int, founderFeeBps: int, founderLockingBytecodeHex: bytes25]</code>. <code>tokenCategory</code> is the categoryId in VM byte order (reversed from the display hex — cashscript's <code>token.category</code> is byte-reversed vs <code>OP_*TOKENCATEGORY</code>). Fetch these from the DVMB OP_RETURN sidecar on the genesis tx, or from our Katalogos entry's parameters chunk.</li>
<li><strong>Address derivation:</strong> rebuild <code>new Contract(artifact, [cat, rate, feeBps, founderLB])</code> locally and verify <code>contract.tokenAddress</code> equals the on-chain reservoir address before trusting any sidecar params.</li>
</ul>
@ -220,7 +228,7 @@ output [1] dust padding at payer [only if leftoverBacking &lt; 1000 sa
<h2 id="devs">For developers</h2>
<p>Everything a dApp needs to interact with an MBA sits in two files:</p>
<ul>
<li><code>contracts/DeviantOpenMintFT.cash</code> — the Static MBA covenant. Share and Oracle variants live beside it.</li>
<li><code>contracts/CoinMBA.cash</code> — the Static MBA covenant. <code>ShareMBA.cash</code> and <code>OracleMBA.cash</code> live beside it; <code>DynamicMBA.cash</code> is the reserved slot for the DRA covenant.</li>
<li><code>design/potidaea/src/orbital.ts</code> — the reference client: builds claim and redeem transactions with cashscript, broadcasts via a multi-source fan-out (own-node BCHN + ephemeral public Fulcrums).</li>
</ul>
<p>A claim transaction's shape is deterministic:</p>