The RadiantMM contract implements a Constant Product Market Maker (CPMM) using Radiant's UTXO model. Each pool is an independent micro-pool that enforces the invariant K = x * y where x is RXD amount and y is token amount.
[CODE_PORTION][OP_STATESEPARATOR][STATE_PORTION]
| Section | Description | Size |
|---|---|---|
| Code Portion | Immutable CPMM logic | ~200-400 bytes |
| State Separator | OP_STATESEPARATOR (0xbd) |
1 byte |
| State Portion | Mutable pool state | ~30 bytes |
OP_DEPTH // Check if withdrawal (sig+pk provided)
OP_IF
// Withdrawal path
OP_DUP OP_HASH160
<20:owner_pkh>
OP_EQUALVERIFY OP_CHECKSIG
OP_ELSE
// Trade path
[Contract continuity verification]
[K calculation and enforcement]
OP_ENDIF
<8:token_amount><32:token_ref>
| Field | Type | Size | Description |
|---|---|---|---|
| token_amount | int64 | 8 bytes | Current token quantity in pool |
| token_ref | bytes32 | 32 bytes | Glyph token reference (singleton ID) |
During trade execution:
- Input state: Read from
tx.inputs[activeInputIndex]state portion - Output state: Read from
tx.outputs[activeInputIndex]state portion
The contract verifies the code portion remains identical while allowing the state portion to change.
Trigger: unlockScript contains <signature> <pubkey>
Validation:
HASH160(pubkey) == ownerPkh(embedded in code)CHECKSIG(signature, pubkey)passes
Constraints: None on outputs - owner has full control
Use Case: Owner withdraws liquidity from pool
Trigger: unlockScript is empty
Validation:
- Output bytecode at same index equals input bytecode (continuity)
K_output >= K_input(constant product maintained)
Constraints:
- Pool must continue at same output index
- Token amounts must be correctly updated in state
- RXD + fee must maintain or increase K
K = RXD_amount × Token_amount
For a valid trade:
K_out = (RXD_out - fee) × Token_out
K_in = RXD_in × Token_in
require(K_out >= K_in)
fee = |RXD_out - RXD_in| × 3 / 1000 // 0.3%
Due to 64-bit signed arithmetic:
- Max safe value:
2^63 - 1 = 9,223,372,036,854,775,807 - Max K before overflow:
~9.2e18 - Recommended max per asset:
~3e9(3 billion satoshis / tokens)
To prevent dust and rounding exploits:
- Minimum RXD: 546 satoshis (dust limit)
- Minimum token: 1 unit
- Minimum K: 546
token_amount = 0: Pool is RXD-only, invalid for tradingrxd_amount = 0: Pool is empty, invalid for trading- Both pools should be withdrawn by owner, not traded
The contract should verify before multiplication:
if (rxd > MAX_SAFE / token) revert overflow
Where MAX_SAFE = 2^62 to allow for fee calculations.
Initial State:
RXD: 10,000 sat
Token: 1,000 units
K: 10,000,000
Trade: Add 1,000 RXD to buy tokens
New RXD: 11,000 sat
New Token: 10,000,000 / 11,000 = 909 units (rounded down)
Tokens received: 1,000 - 909 = 91 tokens
Fee: 1,000 × 3 / 1000 = 3 sat
Verification:
K_in = 10,000 × 1,000 = 10,000,000
K_out = (11,000 - 3) × 909 = 9,991,863
ERROR: K_out < K_in! Trade rejected.
Correct calculation:
Required new token = ceil(K / (rxd_out - fee))
= ceil(10,000,000 / 10,997) = 910 tokens
Tokens received: 90 tokens
K_out = 10,997 × 910 = 10,007,270 ✓
Initial State:
RXD: 10,000 sat
Token: 1,000 units
K: 10,000,000
Trade: Add 100 tokens to get RXD
New Token: 1,100 units
New RXD: 10,000,000 / 1,100 = 9,090 sat (rounded down)
RXD received: 10,000 - 9,090 = 910 sat
Fee: 910 × 3 / 1000 = 2 sat (paid by trader)
Net RXD received: 908 sat
Verification:
K_in = 10,000 × 1,000 = 10,000,000
K_out = (9,090 + 2) × 1,100 = 10,001,200 ✓
Unlock Script: <signature> <pubkey>
Validation:
HASH160(pubkey) == ownerPkh: true
CHECKSIG(sig, pk): true
Result: Script returns true, owner can spend to any outputs
Not applicable - UTXO model is inherently atomic.
Vulnerable to miner/mempool front-running. Mitigations:
- Slippage tolerance in SDK
- Future: commit-reveal scheme
Mitigated by:
- Maximum pool size limits
- SDK-side validation before tx construction
Prevented by bytecode continuity check - attacker cannot modify the code portion.
Radiant has no opcode that reads the global circulating supply S of a fungible ref. tx.inputs.refValueSum($ref) sums only co-spent inputs and tx.outputs.refValueSum($ref) sums only created outputs — both are scoped to the current transaction, and the spender chooses which of their own carriers to include. Any covenant that computes a supply-relative rate against this value is exploitable:
- A proportional burn payout
payout = burned * R / S_seenlets a burner who co-spends only their own shares setS_seen == their own stake, sopayout = R— the whole reserve drains in one tx. - A proportional mint
minted = dR * S / Rbricks on an honest add when no passthrough carrier is co-spent (S_seen = 0).
This is the single root cause that failed all three LP-share drafts (see docs/LP-SHARE-COVENANT-DESIGN.md §0–1, §8.2).
Safe pattern — pin a 1:1 collateral DELTA, never an absolute refValueSum. RadiantSwap's Market.rxd split/merge (lines 28–81, already in production) computes the collateral delta n = out[0].value - in[0].value and requires the ref-value delta to match it:
int n = tx.outputs[0].value - tx.inputs[0].value;
require(n > 0);
require(tx.outputs.refValueSum($shareRef) - tx.inputs.refValueSum($shareRef) == n);
A delta is presence- and padding-invariant (any passthrough carrier cancels between the input and output sums), so it is not spender-selectable. Where a share→reserve scale is genuinely unavoidable (LP shares), use an authenticated in-controller shareTotal scalar that the singleton controller is the sole authority to mutate, with every mutation itself delta-pinned — never an absolute refValueSum denominator. See docs/LP-SHARE-COVENANT-DESIGN.md.
| Opcode | Hex | Description |
|---|---|---|
| OP_DEPTH | 0x74 | Push stack depth |
| OP_IF | 0x63 | Begin if block |
| OP_ELSE | 0x67 | Begin else block |
| OP_ENDIF | 0x68 | End if block |
| OP_DUP | 0x76 | Duplicate top stack item |
| OP_HASH160 | 0xa9 | SHA256 + RIPEMD160 |
| OP_EQUALVERIFY | 0x88 | Verify equality, abort if false |
| OP_CHECKSIG | 0xac | Verify signature |
| OP_INPUTINDEX | 0xc0 | Push current input index |
| OP_UTXOVALUE | 0xc5 | Push UTXO value at index |
| OP_OUTPUTVALUE | 0xc6 | Push output value at index |
| OP_UTXOBYTECODE | 0xc7 | Push UTXO bytecode at index |
| OP_OUTPUTBYTECODE | 0xc8 | Push output bytecode at index |
| OP_MUL | 0x95 | Multiply |
| OP_DIV | 0x96 | Divide |
| OP_ABS | 0x90 | Absolute value |
| OP_SUB | 0x94 | Subtract |
| OP_GREATERTHANOREQUAL | 0xa2 | Compare >= |
| OP_STATESEPARATOR | 0xbd | Separates code from state |