Radiant Smart Contract Best Practices

Version 1.0 Last Updated: February 2026

V2 Hard Fork (Radiant Core 2.1, Block 410,000): Six new/re-enabled opcodes are available post-fork: OP_BLAKE3 (0xee), OP_K12 (0xef) for on-chain hash verification; OP_LSHIFT (0x98), OP_RSHIFT (0x99) for bitwise shifts; OP_2MUL (0x8d), OP_2DIV (0x8e) for numeric scaling. Use these for on-chain PoW validation (dMint v2) and ASERT-lite DAA computation. Contracts using these opcodes must only be deployed after the activation height.


1. Contract Design Principles

1.1 Keep It Simple

// ✅ GOOD: Simple, focused contract
contract SimpleTransfer(bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
}

// ❌ BAD: Overly complex single contract
contract DoEverything(...)
function (...) {
    // 500 lines of logic — hard to audit
}

1.2 Single Responsibility

Contract TypeResponsibility
Token ContractHold and transfer value
Minting ContractCreate new tokens
Swap ContractExchange tokens
Governance ContractVoting and proposals

1.3 Explicit Over Implicit

// ✅ GOOD: Explicit checks
function transfer(sig s, pubkey pk, int amount) {
    require(amount > 0);
    require(amount <= tx.inputs[this.activeInputIndex].value);
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
}

// ❌ BAD: Relying on implicit behavior
function transfer(sig s, pubkey pk, int amount) {
    require(checkSig(s, pk));  // Assumes amount is valid
}

2. Security Patterns

2.1 Standard P2PKH Authorization

function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
}

2.2 Multi-Signature Authorization

// 2-of-3 multisig pattern
contract MultiSig(pubkey PK1, pubkey PK2, pubkey PK3)
function (sig s1, sig s2) {
    int valid = 0;
    if (checkSig(s1, PK1)) valid = valid + 1;
    if (checkSig(s2, PK2)) valid = valid + 1;
    if (checkSig(s1, PK3) || checkSig(s2, PK3)) valid = valid + 1;
    require(valid >= 2);
}

2.3 Time-Locked Authorization

contract TimeLock(bytes20 OWNER_PKH, int UNLOCK_TIME)
function (sig s, pubkey pk) {
    require(tx.time >= UNLOCK_TIME);
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
}

2.4 Value Conservation (CRITICAL for token contracts)

// ✅ GOOD: Explicit conservation check
contract FungibleToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));

    stateSeparator;
    bytes36 ref = pushInputRef(REF);
    bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);

    // CONSERVATION: inputs >= outputs
    int inputSum = tx.inputs.codeScriptValueSum(csh);
    int outputSum = tx.outputs.codeScriptValueSum(csh);
    require(inputSum >= outputSum);
}

2.5 Induction Proof Patterns

Code-Continuity Induction

Verify that the parent UTXO used the same contract code. Since the parent also verified its parent, this creates an inductive chain guaranteeing all ancestors followed the same rules:

// ✅ GOOD: Code-continuity induction step
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
require(tx.outputs.codeScriptCount(csh) >= 1);
require(tx.inputs.codeScriptValueSum(csh) >= tx.outputs.codeScriptValueSum(csh));

Fee-Bounded Transfers

// ✅ GOOD: Prevent excessive fee extraction
int fee = tx.state.inputSum - tx.state.outputSum;
require(fee >= 0);
require(fee <= maxFee);

2.6 Overflow Protection

// ✅ SAFE: Pre-check for overflow
function safeCalculation(int a, int b) {
    require(a >= 0 && b >= 0);
    require(a <= 9223372036854775807 / b);  // MAX_INT64 / b
    int result = a * b;
}

3. Token Contract Patterns

3.1 Fungible Token (FT)

contract FungibleToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
    stateSeparator;
    bytes36 ref = pushInputRef(REF);
    bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
    require(tx.inputs.codeScriptValueSum(csh) >= tx.outputs.codeScriptValueSum(csh));
    require(tx.outputs.refOutputCount(ref) == tx.outputs.codeScriptOutputCount(csh));
}

3.2 Non-Fungible Token (NFT)

contract NFT(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
    stateSeparator;
    bytes36 ref = pushInputRefSingleton(REF);
    require(tx.outputs.refOutputCount(ref) == 1);
}

3.3 NFT with Royalties

contract RoyaltyNFT(
    bytes36 REF, bytes20 OWNER_PKH,
    bytes20 CREATOR_PKH, int ROYALTY_BPS
)
function (sig s, pubkey pk) {
    require(hash160(pk) == OWNER_PKH);
    require(checkSig(s, pk));
    stateSeparator;
    bytes36 ref = pushInputRefSingleton(REF);
    require(tx.outputs.refOutputCount(ref) == 1);

    int inputValue = tx.inputs[this.activeInputIndex].value;
    int outputValue = tx.outputs[0].value;
    if (outputValue < inputValue) {
        int saleProceeds = inputValue - outputValue;
        int royalty = saleProceeds * ROYALTY_BPS / 10000;
        // Verify royalty payment to creator...
    }
}

4. Testing Strategies

4.1 Unit Testing

describe('FungibleToken', () => {
  it('should allow owner to transfer', async () => {
    const tx = await contract.functions.transfer(sig, pk, amount);
    expect(tx).to.exist;
  });

  it('should reject non-owner transfer', async () => {
    await expect(contract.functions.transfer(wrongSig, wrongPk, amount)).to.be.rejected;
  });

  it('should enforce conservation', async () => {
    await expect(contract.functions.transfer(sig, pk, inputAmount + 1)).to.be.rejected;
  });
});

4.2 Debugging with rxdeb

# Debug a specific transaction
rxdeb --tx=<spending_tx_hex> --txin=<input_tx_hex>

# Debug RadiantScript artifact
rxdeb --artifact=MyContract.json --function=transfer --args='["sig", "pk"]'

# Step through execution
rxdeb> step
rxdeb> stack
rxdeb> source

5. Gas and Size Optimization

Script Size Limits

LimitValueNotes
Max script size32 MBConsensus limit
Recommended max10 KBReasonable fees
Optimal< 1 KBMost efficient

Optimization Techniques

// ✅ GOOD: Reuse computed values
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
int inputSum = tx.inputs.codeScriptValueSum(csh);
int outputSum = tx.outputs.codeScriptValueSum(csh);

// ❌ BAD: Recompute same values
require(tx.inputs.codeScriptValueSum(hash256(...)) >= ...);
require(tx.outputs.codeScriptValueSum(hash256(...)) <= ...);

6. Common Vulnerabilities

6.1 Missing Conservation Check

// ❌ VULNERABLE: Attacker can create infinite tokens!
contract BadToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
    require(checkSig(s, pk));
    stateSeparator;
    pushInputRef(REF);
    // Missing: inputSum >= outputSum check
}

6.2 Wrong Reference Type

// ❌ VULNERABLE: Using normal ref for NFT allows duplication
pushInputRef(REF);  // Should be pushInputRefSingleton!

6.3 Integer Overflow

// ❌ VULNERABLE: No overflow check
int result = a * b;  // Can overflow!

// ✅ SAFE: Pre-check
require(a <= 9223372036854775807 / b);
int result = a * b;

6.4 Missing Output Validation

// ❌ VULNERABLE: Accepts any outputs — attacker can drain funds
// ✅ SAFE: Validate outputs explicitly
require(tx.outputs[0].value >= expectedOutput);
require(tx.outputs[0].lockingBytecode == expectedScript);

7. Auditing Checklist

Authorization

Token Mechanics

Arithmetic

Edge Cases

Testing


8. Deployment Guidelines

Pre-Deployment

# 1. Compile with all checks
npx rxdc MyContract.rxd -o MyContract.json --debug

# 2. Run full test suite
npm test

# 3. Audit the compiled output
rxdeb --artifact=MyContract.json --decode

# 4. Check script size
npx rxdc MyContract.rxd --size

Gradual Rollout

  1. Phase 1: Internal testing (1 week)
  2. Phase 2: Testnet public beta (2 weeks)
  3. Phase 3: Mainnet limited (small amounts)
  4. Phase 4: Mainnet full launch

Quick Reference

Do's ✅

Don'ts ❌

For debugging help, see the rxdeb documentation. For security audits, contact the Radiant community.